diff --git a/app/assets/javascripts/api/vulnerability.js.coffee b/app/assets/javascripts/api/vulnerability.js.coffee index ecd0bc5c7..6d0251f51 100644 --- a/app/assets/javascripts/api/vulnerability.js.coffee +++ b/app/assets/javascripts/api/vulnerability.js.coffee @@ -118,7 +118,6 @@ $(document).ready -> $('.bdsa-search-clear').show() else $('.bdsa-search-clear').hide() - $('.bdsa-search-error').hide() $('.bdsa-search-clear').click () -> $('.bdsa-search-input').val('').focus() $(this).hide() diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 1c991246d..4cdcae0b5 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -31,6 +31,13 @@ #= require slick.min +$(document).on 'click', '.flash-close', (e) -> + e.preventDefault() + $alert = $(e.currentTarget).closest('.alert') + $flashMsg = $alert.closest('#flash-msg') + $target = if $flashMsg.length then $flashMsg else $alert + $target.fadeOut 200, -> $target.remove() + $(document).on 'page:change', -> StackShow.init() Expander.init() diff --git a/app/assets/javascripts/charts.js b/app/assets/javascripts/charts.js index b6469f31b..4ec173d41 100644 --- a/app/assets/javascripts/charts.js +++ b/app/assets/javascripts/charts.js @@ -148,6 +148,12 @@ var Charts = { width: 950, lang: { thousandsSep: ',' + }, + exporting: { + enabled: false + }, + credits: { + enabled: false } }) @@ -182,6 +188,7 @@ var Charts = { } }, + commit_volume_formatter: function() { return '' + this.series.name + '
' + this.y + ' Commits (' + Math.floor(this.percentage) + '%)'; } diff --git a/app/assets/javascripts/compare_accordion.js b/app/assets/javascripts/compare_accordion.js new file mode 100644 index 000000000..f6896d33e --- /dev/null +++ b/app/assets/javascripts/compare_accordion.js @@ -0,0 +1,30 @@ +// Mobile Compare Projects Accordion Functionality +document.addEventListener('click', function(e) { + var accordionHeader = e.target.closest('.accordion-header'); + if (accordionHeader) { + e.preventDefault(); + + var section = accordionHeader.parentElement; + var content = section.querySelector('.accordion-content'); + var icon = accordionHeader.querySelector('i'); + var isOpen = section.classList.contains('active'); + + if (isOpen) { + // Close this section + section.classList.remove('active'); + content.style.maxHeight = null; + if (icon) { + icon.classList.remove('icon-chevron-up'); + icon.classList.add('icon-chevron-down'); + } + } else { + // Open this section + section.classList.add('active'); + content.style.maxHeight = content.scrollHeight + 'px'; + if (icon) { + icon.classList.remove('icon-chevron-down'); + icon.classList.add('icon-chevron-up'); + } + } + } +}); diff --git a/app/assets/javascripts/contributors_cards.js b/app/assets/javascripts/contributors_cards.js new file mode 100644 index 000000000..59e5bedd2 --- /dev/null +++ b/app/assets/javascripts/contributors_cards.js @@ -0,0 +1,33 @@ +document.addEventListener('click', function(e) { + // Handle contributor and enlistment card headers + var cardHeader = e.target.closest('.contributor-card-item .card-item-header, .enlistment-card-item .card-item-header'); + if (cardHeader) { + // Skip if clicking a link + if (e.target.closest('a, .contributor-link')) { + return; + } + e.preventDefault(); + cardHeader.closest('.contributor-card-item, .enlistment-card-item').classList.toggle('expanded'); + return; + } + + // Handle about code locations card header + var aboutHeader = e.target.closest('.about-code-locations-card .card-header'); + if (aboutHeader) { + aboutHeader.closest('.about-code-locations-card').classList.toggle('expanded'); + return; + } + + // Handle about account basics card header + var basicsHeader = e.target.closest('.about-account-basics-card .card-header'); + if (basicsHeader) { + basicsHeader.closest('.about-account-basics-card').classList.toggle('expanded'); + return; + } + + // Handle about project basics card header + var projectBasicsHeader = e.target.closest('.about-project-basics-card .card-header'); + if (projectBasicsHeader) { + projectBasicsHeader.closest('.about-project-basics-card').classList.toggle('expanded'); + } +}); diff --git a/app/assets/javascripts/demographics.js.coffee b/app/assets/javascripts/demographics.js.coffee index 69a951832..df770485c 100644 --- a/app/assets/javascripts/demographics.js.coffee +++ b/app/assets/javascripts/demographics.js.coffee @@ -1,21 +1,51 @@ ProjectDemographics = + chart: null + + DESCRIPTIONS: + 'Inactive': 'No recent activity' + 'Very Low': 'Minimal activity' + 'Low': 'Low activity' + 'Moderate': 'Moderate activity' + 'High': 'High activity' + 'Very High': 'Very high activity' + 'New': 'Newly added project' + init: () -> - return if $('#project_demographics').length == 0 + return if $('#demographics_chart').length == 0 $.ajax url: $('#demographics_chart').data('src') cache: false success: (data) -> return if (data == null) - chart = new Highcharts.Chart(data); - ProjectDemographics.tooltip_formatter(chart) + data.tooltip ||= {} + data.tooltip.formatter = () -> + name = if @point?.name then @point.name else @series?.name or '' + pct = if @y? then @y else 0 + desc = ProjectDemographics.DESCRIPTIONS[name] or '' + isMobile = window.innerWidth <= 768 + if isMobile + "#{name}#{pct}%" + else + descHtml = if desc then "#{desc}" else '' + "#{name}#{descHtml}#{pct}%" + + # On touch/mobile devices, disable allowPointSelect so tap shows tooltip + # instead of triggering slice selection animation + isTouch = 'ontouchstart' of window or navigator.maxTouchPoints > 0 + if isTouch + data.plotOptions ||= {} + data.plotOptions.pie ||= {} + data.plotOptions.pie.allowPointSelect = false + data.plotOptions.pie.stickyTracking = true - tooltip_formatter: (chart) -> - chart.tooltip.options.formatter = () -> - if this.point.name - "#{this.point.name}: #{this.y}%" - else - "#{this.series.name}: #{this.y}%" + ProjectDemographics.chart = new Highcharts.Chart(data) + + reflow: () -> + ProjectDemographics.chart.reflow() if ProjectDemographics.chart $(document).on 'page:change', -> ProjectDemographics.init() + +$(window).on 'resize', -> + ProjectDemographics.reflow() diff --git a/app/assets/javascripts/dropdown_handler.js b/app/assets/javascripts/dropdown_handler.js new file mode 100644 index 000000000..f9aea4967 --- /dev/null +++ b/app/assets/javascripts/dropdown_handler.js @@ -0,0 +1,71 @@ +// Dropdown menu handler for logged user menu +(function($) { + 'use strict'; + + function initDropdown() { + var $dropdown = $('#logged_user_menu'); + + if ($dropdown.length === 0) { + return; // Dropdown not found (user not logged in) + } + + var $toggle = $dropdown.find('.dropdown-toggle'); + var $menu = $dropdown.find('.dropdown-menu'); + + // Remove any existing event handlers + $toggle.off('click.userDropdown'); + $(document).off('click.userDropdown keydown.userDropdown'); + $menu.find('a').off('click.userDropdown'); + + // Toggle dropdown on click + $toggle.on('click.userDropdown', function(e) { + e.preventDefault(); + e.stopPropagation(); + + var wasOpen = $dropdown.hasClass('open'); + + // Close all dropdowns first + $('.dropdown').removeClass('open'); + + // Toggle this dropdown + if (!wasOpen) { + $dropdown.addClass('open'); + } + + return false; + }); + + // Close dropdown when clicking outside + $(document).on('click.userDropdown', function(e) { + var $target = $(e.target); + if (!$dropdown.is($target) && $dropdown.has($target).length === 0) { + $dropdown.removeClass('open'); + } + }); + + // Close dropdown when clicking a menu item + $menu.find('a').on('click.userDropdown', function() { + setTimeout(function() { + $dropdown.removeClass('open'); + }, 150); + }); + + // Close dropdown on ESC key + $(document).on('keydown.userDropdown', function(e) { + if (e.keyCode === 27) { + $dropdown.removeClass('open'); + } + }); + } + + // Initialize when DOM is ready + $(document).ready(function() { + initDropdown(); + }); + + // Re-initialize on Turbolinks page change + $(document).on('page:change', function() { + initDropdown(); + }); + +})(jQuery); diff --git a/app/assets/javascripts/enlistments.js.coffee b/app/assets/javascripts/enlistments.js.coffee index 43df16d43..9dd71da81 100644 --- a/app/assets/javascripts/enlistments.js.coffee +++ b/app/assets/javascripts/enlistments.js.coffee @@ -14,7 +14,7 @@ class App.EnlistmentSelect showSpinnerAndSubmit = -> $(this).attr('disabled', 'disabled') $('.enlistment .spinner').show() - $('.well.enlistment form').submit() + $('.enlistment form').submit() hideAllScmInfo = -> $('.enlistment .scm_info').hide() diff --git a/app/assets/javascripts/explore.js.coffee b/app/assets/javascripts/explore.js.coffee index 10e85e195..7695531cf 100644 --- a/app/assets/javascripts/explore.js.coffee +++ b/app/assets/javascripts/explore.js.coffee @@ -1,6 +1,19 @@ +$(document).on 'click', '.tag-search-btn', -> + tag = $(this).siblings('.tag-input').val() + if tag.length > 0 + window.location.href = '/tags?names=' + encodeURIComponent(tag) + return false + +$(document).on 'keydown', '.tag-input', (e) -> + if e.which == 13 + e.preventDefault() + tag = $(this).val() + if tag.length > 0 + window.location.href = '/tags?names=' + encodeURIComponent(tag) + return false App.Explore = init: () -> - return if $('#explore_projects_page').length == 0 + return if $('#explore_projects_page').length == 0 && $('.explore-projects-page').length == 0 $('#explore_search_form .icon-search').click (e) -> e.preventDefault() @@ -13,16 +26,16 @@ App.Explore = $(this).siblings('.icon-search').trigger('click') return false - $('.similar_projects .icon-search').click (e) -> + $('.similar_projects .icon-search, .similar-card .search-icon-btn').click (e) -> $(this).parents('form:first').trigger('submit') $('form[rel=similar_project_jump]').submit (e) -> - projectId = $("#project").val() + projectId = $(this).find('input[name="project"]').val() if projectId != '' e.preventDefault() window.location.href = "/p/#{projectId.toLowerCase()}/similar" else - $('span.error').removeClass('hidden') + $(this).find('span.error').removeClass('hidden') false $('form[rel=sort_filter] select').change () -> @@ -30,5 +43,76 @@ App.Explore = $(this).attr('disabled', 'disabled') $(this).parents('form').attr('action', document.location).submit() + # Discover More collapsible toggle (mobile/tablet) + $(document).on 'click', '.discover-toggle', (e) -> + content = $(this).siblings('.discover-content') + content.toggleClass('show') + chevron = $(this).find('.chevron') + chevron.text(if content.hasClass('show') then '▲' else '▼') + + # Language filter dropdown toggle + $(document).on 'click', '.language-toggle', (e) -> + e.stopPropagation() + menu = $(this).siblings('.language-dropdown-menu') + menu.toggleClass('show') + + $(document).on 'click', (e) -> + unless $(e.target).closest('.language-dropdown').length + $('.language-dropdown-menu').removeClass('show') + $(document).on 'page:change', -> App.Explore.init() + +# ── PAI Tooltip ────────────────────────────────────────────────────────────── +# Runs once at module level; uses event delegation so no re-binding needed. +# position:fixed tooltip appended to escapes overflow/transform clipping. +$('
').appendTo('body') + +stripPaiTitles = -> + $('.pai-tooltip-wrapper [title]').each -> + $(this).removeAttr('title') + +showPaiTooltip = (wrapper) -> + text = wrapper.data('tooltip') + return unless text + # Strip native title to prevent browser double-tooltip + wrapper.find('[title]').removeAttr('title') + tooltip = $('#pai-global-tooltip') + tooltip.text(text) + tooltip.css(visibility: 'hidden', display: 'block') + tw = tooltip.outerWidth() + th = tooltip.outerHeight() + tooltip.css(visibility: '', display: 'none') + rect = wrapper[0].getBoundingClientRect() + left = rect.left + (rect.width / 2) - (tw / 2) + top = rect.top - th - 10 + left = Math.max(8, Math.min(left, window.innerWidth - tw - 8)) + if top < 8 + top = rect.bottom + 10 + tooltip.css(left: left, top: top).addClass('visible') + +hidePaiTooltip = -> + $('#pai-global-tooltip').removeClass('visible') + +# Desktop hover +$(document).on 'mouseenter', '.pai-tooltip-wrapper', -> + showPaiTooltip($(this)) +$(document).on 'mouseleave', '.pai-tooltip-wrapper', -> + hidePaiTooltip() + +# Mobile/tablet tap toggle +$(document).on 'touchstart', '.pai-tooltip-wrapper', (e) -> + e.preventDefault() + e.stopPropagation() + if $('#pai-global-tooltip').hasClass('visible') + hidePaiTooltip() + else + showPaiTooltip($(this)) +$(document).on 'touchstart', (e) -> + unless $(e.target).closest('.pai-tooltip-wrapper').length + hidePaiTooltip() + +# Strip native [title] on every page load (prevents browser double-tooltip) +document.addEventListener 'DOMContentLoaded', stripPaiTitles +document.addEventListener 'turbolinks:load', stripPaiTitles +document.addEventListener 'page:change', stripPaiTitles diff --git a/app/assets/javascripts/home.js.coffee b/app/assets/javascripts/home.js.coffee index 0e23b0b5b..560e1b882 100644 --- a/app/assets/javascripts/home.js.coffee +++ b/app/assets/javascripts/home.js.coffee @@ -2,7 +2,7 @@ $(document).on 'page:change', -> divs = $('p[id^="content-"]') i = 0 do -> - divs.eq(i).removeClass('hide').fadeIn(400).delay(6000).fadeOut 400, arguments.callee + divs.eq(i).removeClass('hide').fadeIn(200).delay(2500).fadeOut 200, arguments.callee i = ++i % divs.length $('#icon_text').click -> diff --git a/app/assets/javascripts/language_dropdown.js b/app/assets/javascripts/language_dropdown.js new file mode 100644 index 000000000..dc6555d17 --- /dev/null +++ b/app/assets/javascripts/language_dropdown.js @@ -0,0 +1,88 @@ +// Language Dropdown Menu Handler +$(document).on('click', '.language-dropdown-trigger', function(e) { + e.preventDefault(); + e.stopPropagation(); + + var $trigger = $(this); + var $wrapper = $trigger.closest('.language-dropdown-wrapper'); + var $menu = $wrapper.find('.language-dropdown-menu'); + var $icon = $trigger.find('i'); + + // Close all other language dropdowns + $('.language-dropdown-menu.active').not($menu).removeClass('active'); + $('.language-dropdown-trigger i').removeClass('icon-chevron-up').addClass('icon-chevron-down'); + + // Toggle this dropdown + $menu.toggleClass('active'); + + // Update chevron icon + if ($menu.hasClass('active')) { + $icon.removeClass('icon-chevron-down').addClass('icon-chevron-up'); + } else { + $icon.removeClass('icon-chevron-up').addClass('icon-chevron-down'); + } +}); + +// Handle language dropdown item selection +$(document).on('click', '.language-dropdown-menu .dropdown-item', function(e) { + e.preventDefault(); + e.stopPropagation(); + + var $item = $(this); + var $menu = $item.closest('.language-dropdown-menu'); + var $wrapper = $menu.closest('.language-dropdown-wrapper'); + var $trigger = $wrapper.find('.language-dropdown-trigger'); + var $selection = $trigger.find('.selection'); + var $input = $wrapper.find('.language-input'); + var $icon = $trigger.find('i'); + + // Get selected value and text + var selectedValue = $item.data('value'); + var selectedText = $item.text().trim(); + + // Update display and input + $selection.text(selectedText); + $input.val(selectedValue); + + // Update active state + $menu.find('.dropdown-item').removeClass('active'); + $item.addClass('active'); + + // Close dropdown + $menu.removeClass('active'); + $icon.removeClass('icon-chevron-up').addClass('icon-chevron-down'); +}); + +// Close language dropdowns when clicking outside +$(document).on('click', function(e) { + if (!$(e.target).closest('.language-dropdown-wrapper').length) { + var $openMenu = $('.language-dropdown-menu.active'); + if ($openMenu.length) { + $openMenu.removeClass('active'); + $openMenu.closest('.language-dropdown-wrapper').find('.language-dropdown-trigger i') + .removeClass('icon-chevron-up').addClass('icon-chevron-down'); + } + } +}); + +// Initialize active item styling on page load +$(document).on('page:change', function() { + $('.language-dropdown-wrapper').each(function() { + var $wrapper = $(this); + var $input = $wrapper.find('.language-input'); + var selectedValue = $input.val(); + + if (selectedValue) { + // Find and mark the matching dropdown item as active + $wrapper.find('.dropdown-item').each(function() { + var $item = $(this); + if ($item.data('value') === selectedValue) { + $item.addClass('active'); + // Update trigger display + var $selection = $wrapper.find('.language-dropdown-trigger .selection'); + $selection.text($item.text().trim()); + } + }); + } + }); +}); diff --git a/app/assets/javascripts/mobile_menu.js b/app/assets/javascripts/mobile_menu.js new file mode 100644 index 000000000..a8164ec25 --- /dev/null +++ b/app/assets/javascripts/mobile_menu.js @@ -0,0 +1,70 @@ +// Mobile Menu Toggle Functionality +var MobileMenu = { + init: function() { + this.bindEvents(); + }, + + toggleMenu: function() { + var mobileMenu = document.getElementById('mobile-menu'); + + if (mobileMenu) { + if (mobileMenu.classList.contains('show')) { + mobileMenu.classList.remove('show'); + } else { + mobileMenu.classList.add('show'); + } + } + }, + + closeMenu: function() { + var mobileMenu = document.getElementById('mobile-menu'); + if (mobileMenu) { + mobileMenu.classList.remove('show'); + } + }, + + bindEvents: function() { + var self = this; + var toggleBtn = document.getElementById('mobile-menu-toggle'); + + if (toggleBtn) { + toggleBtn.onclick = function(e) { + e.preventDefault(); + self.toggleMenu(); + return false; + }; + } + + // Close menu when clicking on a link + var mobileMenuLinks = document.querySelectorAll('.mobile-menu-items a, .mobile-menu-user a, .mobile-menu-signin a'); + + for (var i = 0; i < mobileMenuLinks.length; i++) { + mobileMenuLinks[i].onclick = function() { + self.closeMenu(); + }; + } + + // Close menu when window is resized to desktop size + window.addEventListener('resize', function() { + if (window.innerWidth >= 1024) { + self.closeMenu(); + } + }); + } +}; + +// Initialize on DOM ready +if (typeof $ !== 'undefined') { + $(document).on('page:change', function() { + MobileMenu.init(); + }); + + $(document).ready(function() { + MobileMenu.init(); + }); +} else { + // Fallback if jQuery is not available + document.addEventListener('DOMContentLoaded', function() { + MobileMenu.init(); + }); +} diff --git a/app/assets/javascripts/orgs.js b/app/assets/javascripts/orgs.js index bb71ec81a..70f493696 100644 --- a/app/assets/javascripts/orgs.js +++ b/app/assets/javascripts/orgs.js @@ -1,4 +1,6 @@ var GaugeProgress = { + themeListenerAttached: false, + init: function(){ var scope = this; var page_ids = ['explore_orgs_page']; @@ -14,12 +16,29 @@ var GaugeProgress = { scope.orgs_progress_bar() } }) + + if (!this.themeListenerAttached) { + var themeToggleBtn = document.getElementById('theme-toggle'); + if (themeToggleBtn) { + themeToggleBtn.addEventListener('click', function() { + setTimeout(function() { + scope.init(); + }, 1); + }); + this.themeListenerAttached = true; + } + } }, config: function(data){ + var isDarkTheme = $('html').hasClass('dark'); + var bgColor = isDarkTheme ? '#2D1548' : '#FFF'; + var labelColor = isDarkTheme ? '#FFF' : '#000'; + return { chart: { type: 'solidgauge', + backgroundColor: bgColor, }, title: null, pane: { @@ -28,7 +47,7 @@ var GaugeProgress = { startAngle: -90, endAngle: 90, background: { - backgroundColor: '#FFF', + backgroundColor: bgColor, innerRadius: '100%', outerRadius: '45%', shape: 'arc' @@ -65,7 +84,7 @@ var GaugeProgress = { } } }, - series: [{data: [data], dataLabels: { format: '

{y}

'} }], + series: [{data: [data], dataLabels: { format: '

{y}

'} }], exporting: { enabled: false } @@ -86,18 +105,50 @@ var GaugeProgress = { var OrgsFilter = { init: function(){ - $('#explore_orgs_page .chzn-select').chosen().change(function(){ - $('.busy#commit_volume_loader').toggleClass('hidden') - $('#orgs_by_30_days_volume table').toggleClass('hidden') + var filterOrgs = function(filterValue) { + $('.busy#commit_volume_loader').removeClass('hidden') $.ajax({ - url: '/explore/orgs_by_thirty_day_commit_volume?format=js&filter='+ $(this).val(), + url: '/explore/orgs_by_thirty_day_commit_volume?format=js&org_type='+ filterValue, type: "GET", success: function(){ - $('#orgs_by_30_days_volume table').toggleClass('hidden') - $('.busy#commit_volume_loader').toggleClass('hidden') + $('.busy#commit_volume_loader').addClass('hidden') } }) - }) + }; + + // Legacy chosen dropdown support + $('#explore_orgs_page .chzn-select').chosen().change(function(){ + filterOrgs($(this).val()); + }); + + // Custom dropdown for org type filter - handle button toggle and aria-expanded + var $orgDropdown = $('#orgs_by_30_days_volume .custom-sort-dropdown'); + var $orgButton = $orgDropdown.find('.sort-dropdown-btn'); + + $orgButton.on('click', function() { + var isOpen = $orgDropdown.hasClass('open'); + $orgButton.attr('aria-expanded', !isOpen); + }); + + // Close dropdown when clicking outside + $(document).on('click', function(e) { + if (!$(e.target).closest('#orgs_by_30_days_volume .custom-sort-dropdown').length) { + $orgDropdown.removeClass('open'); + $orgButton.attr('aria-expanded', 'false'); + } + }); + + // Handle dropdown item clicks + $('#orgs_by_30_days_volume .sort-dropdown-item').on('click', function(e) { + e.preventDefault(); + var value = $(this).data('value'); + + // Close dropdown and update aria-expanded + $orgDropdown.removeClass('open'); + $orgButton.attr('aria-expanded', 'false'); + + filterOrgs(value); + }); } } @@ -120,5 +171,7 @@ var OrgClaimProject = { } } $(document).ready(function() { - OrgClaimProject.init() + GaugeProgress.init(); + OrgsFilter.init(); + OrgClaimProject.init(); }); diff --git a/app/assets/javascripts/page_loader.js b/app/assets/javascripts/page_loader.js new file mode 100644 index 000000000..d24cf0597 --- /dev/null +++ b/app/assets/javascripts/page_loader.js @@ -0,0 +1,64 @@ +// Show loader immediately when user clicks a link to a slow page. +// Hide it once the destination page's DOM is ready. + +(function() { + var SLOW_PAGE_PATTERNS = [ + /^\/people(\/|\?|$)/, + /^\/explore\/projects(\/|\?|$)/, + /^\/explore\/orgs(\/|\?|$)/, + /^\/committers(\/|\?|$)/, + /^\/accounts(\/|\?|$)/, + /^\/p\/[^/]+\/commits(\/|\?|$)/ + ]; + + function isSlowPage(url) { + try { + var path = new URL(url, window.location.origin).pathname; + return SLOW_PAGE_PATTERNS.some(function(pattern) { + return pattern.test(path); + }); + } catch (e) { + return false; + } + } + + function showLoader() { + var loader = document.getElementById('page-loader'); + if (loader) loader.classList.remove('hidden'); + } + + function hideLoader() { + var loader = document.getElementById('page-loader'); + if (loader) loader.classList.add('hidden'); + } + + // Show loader when clicking links to slow pages + document.addEventListener('click', function(e) { + // Skip if event was already prevented or not a left-click + if (e.defaultPrevented || e.button !== 0) return; + // Skip if modifier keys held (would open in new tab/window) + if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; + + var link = e.target.closest('a[href]'); + if (!link) return; + + // Skip if link targets something other than current window + if (link.target && link.target.toLowerCase() !== '_self') return; + + var href = link.getAttribute('href'); + // Skip hash-only links + if (!href || href.charAt(0) === '#') return; + + if (isSlowPage(href)) { + showLoader(); + } + }); + + // Hide loader once this page's DOM is ready + document.addEventListener('DOMContentLoaded', hideLoader); + + // Hide loader when page is restored from bfcache (browser back/forward) + window.addEventListener('pageshow', function(e) { + if (e.persisted) hideLoader(); + }); +})(); diff --git a/app/assets/javascripts/positions.js.coffee b/app/assets/javascripts/positions.js.coffee index c65a4b227..d677e8572 100644 --- a/app/assets/javascripts/positions.js.coffee +++ b/app/assets/javascripts/positions.js.coffee @@ -63,6 +63,14 @@ class SetupProjectAndLanguagesSections $(this).addClass('hidden') $('a.expanded').removeClass('hidden') $('#project-experience-form').find('input').autocomplete({ source: '/autocompletes/project' }) + # Defer chosen re-init until the browser has reflowed the now-visible section. + # Without setTimeout, chosen reads offsetWidth before layout and gets 0px. + $select = $('#position_languages .chzn-select') + setTimeout -> + $select.chosen('destroy') + $select.chosen() + $('#position_languages .chosen-container').css('width', '100%') + , 0 $('a.expanded').click -> $('#additional-fields').addClass('hidden') $(this).addClass('hidden') diff --git a/app/assets/javascripts/project_badges.js.coffee b/app/assets/javascripts/project_badges.js.coffee index 0f043bdc5..206c175de 100644 --- a/app/assets/javascripts/project_badges.js.coffee +++ b/app/assets/javascripts/project_badges.js.coffee @@ -7,6 +7,11 @@ ProjectNewBadge = initializeNewBadge: (_klass) -> $("#add_badge_btn").on 'click', (event) -> $('#add_badge_btn, #add_new_badge_form').toggle() + # Re-initialize chosen after showing the form row — it was first + # initialized on a display:none element, so width/position may be wrong. + if $('#add_new_badge_form').is(':visible') + $('#add_new_badge_form .chzn-select').chosen('destroy') + $('#add_new_badge_form .chzn-select').chosen() handleEvents: (_klass) -> $('#project_badges_page').on 'change', '#select_project_badge', (event) -> diff --git a/app/assets/javascripts/project_rating.js.coffee b/app/assets/javascripts/project_rating.js.coffee index a3a61a9e0..8f761fe9c 100644 --- a/app/assets/javascripts/project_rating.js.coffee +++ b/app/assets/javascripts/project_rating.js.coffee @@ -1,3 +1,88 @@ +STAR_COLOR_FILLED = '#ffb91a' +STAR_COLOR_EMPTY = '#d1d5db' + +getCsrfToken = -> + meta = document.querySelector('meta[name="csrf-token"]') + if meta then meta.getAttribute('content') else '' + +updateStarDisplay = (buttons, score) -> + buttons.each -> + svg = $(this).find('.star-svg') + rating = parseInt($(this).data('rating'), 10) + if rating <= score + svg.attr('fill', STAR_COLOR_FILLED).attr('stroke', STAR_COLOR_FILLED) + else + svg.attr('fill', 'none').attr('stroke', STAR_COLOR_EMPTY) + +ratingAjax = (method, url, ratingContent) -> + $.ajax + url: url + type: method + headers: + 'X-CSRF-Token': getCsrfToken() + dataType: 'html' + success: (html) -> + if ratingContent.length + ratingContent.replaceWith(html) + initInteractiveStars() + error: (xhr) -> + console.error('Rating request failed:', xhr.status, xhr.statusText) + +initInteractiveStars = -> + $('.interactive-stars').each -> + container = $(this) + return if container.data('bound') + container.data('bound', true) + + buttons = container.find('.star-btn') + rateUrl = container.data('rate-url') + unrateUrl = container.data('unrate-url') + loggedIn = container.data('logged-in') is true or container.data('logged-in') is 'true' + userScore = parseInt(container.data('user-score'), 10) or 0 + showParam = encodeURIComponent(container.data('show') or 'projects/show/community_rating') + + updateStarDisplay(buttons, userScore) + + buttons.each -> + btn = $(this) + rating = parseInt(btn.data('rating'), 10) + + btn.on 'mouseenter', -> + updateStarDisplay(buttons, rating) + + btn.on 'mouseleave', -> + updateStarDisplay(buttons, userScore) + + btn.on 'click', (e) -> + e.preventDefault() + e.stopPropagation() + + unless loggedIn + window.location.href = '/sessions/new?return_to=' + encodeURIComponent(window.location.href) + return + + newScore = if rating is userScore then 0 else rating + ratingContent = container.closest('.rating-content') + + if newScore is 0 + ratingAjax('DELETE', unrateUrl + '?show=' + showParam, ratingContent) + else + ratingAjax('POST', rateUrl + '?score=' + newScore + '&show=' + showParam, ratingContent) + + # Clear rating button + $('.clear-rating-btn').each -> + btn = $(this) + return if btn.data('bound') + btn.data('bound', true) + + btn.on 'click', (e) -> + e.preventDefault() + e.stopPropagation() + url = btn.data('url') + ratingContent = btn.closest('.rating-content') + ratingAjax('DELETE', url, ratingContent) + +# Old jrating-based rating system (used on non-redesigned pages) App.ProjectRating = init: -> $("#rating_spinner").hide() @@ -40,3 +125,4 @@ App.ProjectRating = $(document).on 'page:change', -> App.ProjectRating.init() + initInteractiveStars() diff --git a/app/assets/javascripts/project_swimlanes.js b/app/assets/javascripts/project_swimlanes.js new file mode 100644 index 000000000..270f4e96f --- /dev/null +++ b/app/assets/javascripts/project_swimlanes.js @@ -0,0 +1,30 @@ +// Project Swimlanes - Show More/Less functionality +document.addEventListener('DOMContentLoaded', function() { + var buttons = document.querySelectorAll('.show_more_btn'); + + for (var i = 0; i < buttons.length; i++) { + buttons[i].addEventListener('click', function() { + var swimlane = this.getAttribute('data-swimlane'); + var swimlaneContent = document.querySelector('.swimlane_content[data-swimlane="' + swimlane + '"]'); + var hiddenCards = swimlaneContent.querySelectorAll('.hidden_card'); + var isExpanded = this.textContent.trim() === 'Show Less'; + + // Toggle visibility of hidden cards + for (var j = 0; j < hiddenCards.length; j++) { + if (isExpanded) { + hiddenCards[j].style.display = 'none'; + } else { + hiddenCards[j].style.display = 'block'; + } + console.log('Card', j, 'classes:', hiddenCards[j].className, 'display:', window.getComputedStyle(hiddenCards[j]).display); + } + + // Toggle button text + if (isExpanded) { + this.textContent = 'Show More'; + } else { + this.textContent = 'Show Less'; + } + }); + } +}); diff --git a/app/assets/javascripts/projects.js.coffee b/app/assets/javascripts/projects.js.coffee index 4fb5fb2f4..f7ca1c34d 100644 --- a/app/assets/javascripts/projects.js.coffee +++ b/app/assets/javascripts/projects.js.coffee @@ -32,12 +32,20 @@ class App.ProjectForm class App.SimilarProjects constructor: -> return unless $('#projects_show_page').length - $('#similar_projects').html('') + $desktopDiv = $('#similar_projects') + $mobileDiv = $('#similar_projects_mobile') + return unless $desktopDiv.length || $mobileDiv.length + $desktopDiv.html('') + $mobileDiv.html('') $('#related_spinner').removeClass('hidden') - projectId = $('#similar_projects').data('projectId') + $('#related_spinner_mobile').removeClass('hidden') + projectId = ($desktopDiv.length && $desktopDiv.data('projectId')) || $mobileDiv.data('projectId') $.ajax url: "/p/#{ projectId }/similar_by_tags.js" + success: (data) -> + $mobileDiv.html($desktopDiv.html()) complete: -> $('#related_spinner').addClass('hidden') + $('#related_spinner_mobile').addClass('hidden') $(document).on 'page:change', -> new App.SimilarProjects() diff --git a/app/assets/javascripts/search_dingus.js b/app/assets/javascripts/search_dingus.js index f98d1251a..140dafccc 100644 --- a/app/assets/javascripts/search_dingus.js +++ b/app/assets/javascripts/search_dingus.js @@ -19,59 +19,118 @@ var ohloh = (function builder($) { ui: { init: function(){ var $scope = $('.ux-dropdown'); - var $search_text_field = $scope.find('.text'); - var dropdownHead = $('a span.selection', $scope); - var getSelectedValue = function() { - return dropdownHead.first().attr('val'); - }; - $('.dropdown-menu li a', $scope).click(function() { + var $search_text_field = $scope.find('.text').add($scope.siblings('.header-search-input')); + var dropdownHead = $('button span.selection, a span.selection', $scope); + $('.dropdown-menu .dropdown-item', $scope).click(function() { var selectedText = $(this).attr('val') dropdownHead.attr('val',selectedText) dropdownHead.html( $(this).html() ) - dropdownHead.parents('form').attr('action',selectedText) + // Do NOT set form action here; URL will be built on submit + // Update active class + $('.dropdown-menu .dropdown-item', $scope).removeClass('active') + $(this).addClass('active') if(selectedText != "//code.ohloh.net/search"){ dropdownHead.parents('form').removeAttr('target'); } }); - $('ul.dropdown-menu li a', $scope).on('click', 'a', function() { + $('.dropdown-menu .dropdown-item', $scope).on('click', function() { var $this = $(this); var section_name = $this.text(); - $('ul.dropdown-menu li a', $scope).removeClass('selected'); + $('.dropdown-menu .dropdown-item', $scope).removeClass('selected'); $this.addClass('selected'); - $('.ux-dropdown a span.selection', $scope).html(section_name); + $('.ux-dropdown button span.selection', $scope).html(section_name); }); $(document).ajaxStop($.unblockUI); if( (/\/p(\/|\?|$)/ig).test(window.location.href) ) { - $('a', $scope).removeClass('default'); - $('a[val="p"]', $scope).addClass('default'); + $('.dropdown-item', $scope).removeClass('default'); + $('.dropdown-item[val="p"]', $scope).addClass('default'); } if( (/\/orgs(\/|\?|$)/ig).test(window.location.href) ) { - $('a', $scope).removeClass('default'); - $('a[val="orgs"]', $scope).addClass('default'); + $('.dropdown-item', $scope).removeClass('default'); + $('.dropdown-item[val="orgs"]', $scope).addClass('default'); } if( (/\/(posts|forums)(\/|\?|$)/ig).test(window.location.href) ) { - $('a', $scope).removeClass('default'); - $('a[val="posts"]', $scope).addClass('default'); + $('.dropdown-item', $scope).removeClass('default'); + $('.dropdown-item[val="posts"]', $scope).addClass('default'); } if( (/\/(people|accounts|committers)(\/|\?|$)/ig).test(window.location.href) ) { - $('a', $scope).removeClass('default'); - $('a[val="people"]', $scope).addClass('default'); + $('.dropdown-item', $scope).removeClass('default'); + $('.dropdown-item[val="people"]', $scope).addClass('default'); } - $('a.default', $scope).trigger('click'); + $('.dropdown-item.default', $scope).trigger('click'); - $('.dropdown-menu li a', $scope).on('click', 'a', function(){ + $('.dropdown-menu .dropdown-item', $scope).on('click', function(){ $('input[name="query"].search').trigger('click').focus(); }); + // Toggle sort dropdown open/close on button click. + // NOTE: sort button intentionally has NO data-toggle="dropdown" so Bootstrap's + // dropdown plugin does not interfere — both would toggle .open on the same click + // and cancel each other out, keeping the menu permanently closed. + $(document).on('click', '.sort-dropdown-btn', function() { + var $dropdown = $(this).closest('.custom-sort-dropdown'); + $('.custom-sort-dropdown').not($dropdown).removeClass('open'); + $dropdown.toggleClass('open'); + }); + + // Close any open sort dropdown when clicking outside it + $(document).on('click', function(e) { + if (!$(e.target).closest('.custom-sort-dropdown').length) { + $('.custom-sort-dropdown.open').removeClass('open'); + } + }); + + // Handle custom sort dropdown + $('.custom-sort-dropdown .sort-dropdown-item').on('click', function(e) { + e.preventDefault(); + var $item = $(this); + var $dropdown = $item.closest('.custom-sort-dropdown'); + var selectedValue = $item.data('value'); + var selectedLabel = $item.text(); + + // Update button text + $dropdown.find('.selection').text(selectedLabel).data('value', selectedValue); + + // Update hidden input + $dropdown.find('.sort-value-input').val(selectedValue); + + // Update active class + $dropdown.find('.sort-dropdown-item').removeClass('active'); + $item.addClass('active'); + + // Submit form + $dropdown.closest('form').submit(); + }); + + // Handle search input with cancel button + $('#query').on('input', function() { + var $input = $(this); + var $cancelBtn = $input.siblings('.search-cancel-btn'); + + if ($input.val().length > 0) { + $cancelBtn.show(); + } else { + $cancelBtn.hide(); + } + }); + + // Handle search cancel button + $('.search-cancel-btn').on('click', function() { + var $input = $('#query'); + $input.val(''); + $(this).hide(); + $input.focus(); + }); + if( (/\?[query]=.{1,}/ig).test(window.location.href) ) { var q = window.location.href.split('?')[1].split('=')[1]; @@ -83,34 +142,33 @@ var ohloh = (function builder($) { $search_text_field.keydown(function(e){ if(e.which == 13) { e.preventDefault(); - $(this).siblings('.submit').trigger('click'); + $(this).siblings('.submit, .header-search-btn').trigger('click'); return false; } }); - $search_text_field.siblings('.submit').click(function(e) { - var search_term = $.trim($search_text_field.val()); + $search_text_field.siblings('.submit, .header-search-btn').click(function(e) { + var $btn = $(this); + var $field = $btn.siblings('.text, .header-search-input'); + var $local_scope = $btn.closest('form').find('.ux-dropdown'); + var localDropdownHead = $('button span.selection, a span.selection', $local_scope); + var search_term = $.trim($field.val()); if(search_term.length > 0) { e.preventDefault(); - var section = getSelectedValue(), - url_format = "/#{activator}?"+$search_text_field.attr('name')+"=#{query}"; - - var data = { - activator: section, - query: search_term - }; + var section = localDropdownHead.first().attr('val'); + var url = "/" + section + "?query=" + encodeURIComponent(search_term); - if($search_text_field.attr('name') == 's') - url_format += "&ref=Open%20Hub"; + if($field.attr('name') == 's') + url += "&ref=Open%20Hub"; if(window.location.href.indexOf("sort=") > -1) { sort_value = window.location.href.split('sort=')[1].split('&')[0]; - url_format += "&sort=" + sort_value; + url += "&sort=" + sort_value; } - window.location.href = url_format._f(data); + window.location.href = url; } return false; diff --git a/app/assets/javascripts/session_projects.js.coffee b/app/assets/javascripts/session_projects.js.coffee index a3c968661..0487d47d2 100644 --- a/app/assets/javascripts/session_projects.js.coffee +++ b/app/assets/javascripts/session_projects.js.coffee @@ -9,83 +9,128 @@ SessionProjects = url: "/session_projects?#{ timestamp }" success: SessionProjects.success + # Initialize compare checkboxes + SessionProjects.enable() + enable: -> - # Update all checkboxes on page to match those in the menu. + # Update all compare checkboxes on page to match those in the menu. # This is primarily so that state will be cleaned up after back button. - $('.sp_input').prop('checked', false) - $('.sp_form.styled').removeClass('selected') - $('#sp_menu .sp_input').each -> - $(".sp_input[project_id='#{ $(this).attr('project_id') }']").prop('checked', true) - $("#sp_form_#{ $(this).attr('project_id') }").addClass('selected') + $('.compare-checkbox').removeClass('selected') + $('.compare-checkbox i').removeClass('fa-check-square-o').addClass('fa-square-o') - if $('#sp_menu .sp_input').length < 3 + # Mark selected projects from menu + $('#sp_menu .remove-project').each -> + project_id = $(this).data('project-id') + $(".compare-checkbox[data-project-id='#{ project_id }']").addClass('selected') + $(".compare-checkbox[data-project-id='#{ project_id }'] i").removeClass('fa-square-o').addClass('fa-check-square-o') + + if $('#sp_menu .remove-project').length < 3 # All checkboxes are enabled. - $('.sp_input') - .unbind('change') - .bind('change', SessionProjects.change) - .prop('disabled', false) - .parent().removeClass('disabled') + $('.compare-checkbox') + .off('click') + .on('click', SessionProjects.change) + .removeClass('disabled') else # Project limit reached. Can only uncheck checkboxes. - $('.sp_input') - .filter(':checked') - .unbind('change') - .bind('change', SessionProjects.change) - .prop('disabled', false) - .parent().removeClass 'disabled' + $('.compare-checkbox.selected') + .off('click') + .on('click', SessionProjects.change) + .removeClass('disabled') SessionProjects.disableUnchecked() - # Activate the x icon. - $('#sp_menu .sp_input') - .unbind('click') - .bind('click', SessionProjects.change) + # Activate the remove icons in menu. + $('#sp_menu .remove-project') + .off('click') + .on('click', SessionProjects.remove) - # Animate the appearance/disappearance of menu. - height = '0' - height = '2.6em' if $('#sp_menu .sp_input').length - $('#sp_menu').animate { 'min-height': height }, duration: 300 + # Activate toggle button + $('#compare_toggle_btn') + .off('click') + .on('click', SessionProjects.toggleTray) + + # Show/hide the entire compare bar + if $('#sp_menu .remove-project').length > 0 + $('#sp_menu').fadeIn(200) + $('#projects_index_page').addClass('has-compare-tray') + else + $('#sp_menu').fadeOut(200) + $('#projects_index_page').removeClass('has-compare-tray') disableUnchecked: -> - $('.sp_input') - .not(':checked') - .unbind('change') - .prop('disabled', true) - .parent().addClass('disabled') + $('.compare-checkbox') + .not('.selected') + .off('click') + .addClass('disabled') - busy_span: " " - compare_span: "Compare" + toggleTray: -> + $tray = $('.compare-tray-content') + $icon = $('#compare_toggle_btn .toggle-icon') + + if $tray.hasClass('expanded') + $tray.removeClass('expanded') + $icon.removeClass('rotate') + else + $tray.addClass('expanded') + $icon.addClass('rotate') + false change: -> - checked = $(this).is(':checked') - checked = false if $(this).hasClass('icon-remove-sign') - # force false for remove-icons - project_id = $(this).attr('project_id') - sel = $('.sp_input[project_id="' + project_id + '"]') - sel.siblings('span').replaceWith SessionProjects.busy_span - sel.prop('checked', checked).prop 'disabled', true - SessionProjects.disableUnchecked() - if checked + $checkbox = $(this) + return false if $checkbox.hasClass('disabled') + + is_selected = $checkbox.hasClass('selected') + project_id = $checkbox.data('project-id') + + # Show loading state + $checkbox.addClass('disabled') + + if is_selected + # Uncheck $.ajax type: 'POST' - url: '/session_projects?project_id=' + project_id + url: '/session_projects/' + project_id + data: '_method': 'delete' success: SessionProjects.success + error: -> $checkbox.removeClass('disabled') else + # Check $.ajax type: 'POST' - url: '/session_projects/' + project_id - data: '_method': 'delete' + url: '/session_projects?project_id=' + project_id success: SessionProjects.success + error: (xhr) -> + if xhr.status == 403 + alert(xhr.responseText) + $checkbox.removeClass('disabled') + false + + remove: -> + project_id = $(this).data('project-id') + $.ajax + type: 'POST' + url: '/session_projects/' + project_id + data: '_method': 'delete' + success: SessionProjects.success false success: (data, textStatus, jqXHR) -> + # Remember if the tray was expanded + wasExpanded = $('.compare-tray-content').hasClass('expanded') + + # Replace the HTML $('#sp_menu').html(data) - if _(data).isEmpty() - $('#page').css('margin-top', '0px') - else - $('#page').css('margin-top', '52px') - $('.busy').replaceWith(SessionProjects.compare_span) SessionProjects.enable() + + # Restore expanded state if it was open + if wasExpanded + $('.compare-tray-content').addClass('expanded') + $('#compare_toggle_btn .toggle-icon').addClass('rotate') + false $(document).on 'page:change', -> SessionProjects.init() + +# Also initialize on regular page load +$ -> + SessionProjects.init() diff --git a/app/assets/javascripts/show.js.coffee b/app/assets/javascripts/show.js.coffee index b3011db7d..ffd726517 100644 --- a/app/assets/javascripts/show.js.coffee +++ b/app/assets/javascripts/show.js.coffee @@ -5,7 +5,7 @@ PersonSummaryAccountAbout = PersonSummaryAdminPanel = init: () -> - $('#close_admin_panel, #open_admin_panel').click -> + $('#close_admin_panel, #open_admin_panel, .admin_close_text_btn').click -> $('#admin_actions_opened, #admin_actions_closed').toggleClass('hidden') $(document).on 'page:change', -> PersonSummaryAccountAbout.init() diff --git a/app/assets/javascripts/steamgraph.js b/app/assets/javascripts/steamgraph.js index 108b470e1..6f17968b2 100644 --- a/app/assets/javascripts/steamgraph.js +++ b/app/assets/javascripts/steamgraph.js @@ -120,7 +120,9 @@ Streamgraph = { var svg = d3.select("#ohloh_streamgraph").append("svg") .attr("id", "ohloh_stream") - .attr("width", width) + .attr("viewBox", "0 0 " + width + " " + height) + .attr("preserveAspectRatio", "xMidYMid meet") + .attr("width", "100%") .attr("height", height) .attr('class', 'background-watermark'); @@ -181,11 +183,14 @@ Streamgraph = { }); }, populate_legends: function(legends, colors){ - legend_height = Math.min(legends.length * 18 + 5, 300); - $('#ohloh_streamgraph').after('
'); - legends.map( function(l, i) { - div = "

"+legends[i]+"

"; - $("#streamgraph_legend").append(div); + $('#streamgraph_legend').remove(); + $('#ohloh_streamgraph').after('
'); + legends.map(function(l, i) { + var item = "" + + "" + + "" + l + "" + + ""; + $("#streamgraph_legend").append(item); }); }, diff --git a/app/assets/javascripts/theme_toggle.js b/app/assets/javascripts/theme_toggle.js new file mode 100644 index 000000000..7d6c196bd --- /dev/null +++ b/app/assets/javascripts/theme_toggle.js @@ -0,0 +1,183 @@ +// Theme Toggle Functionality +var ThemeToggle = { + COOKIE_NAME: 'theme_preference', + COOKIE_DAYS: 365, + + init: function() { + var self = this; + this.isAuthenticated = this.checkAuthentication(); + + if (this.isAuthenticated) { + this.loadServerThemePreference(function(theme) { + self.applyTheme(theme); + self.bindEvents(); + }); + } else { + var savedTheme = this.getSavedTheme(); + this.applyTheme(savedTheme); + this.bindEvents(); + } + }, + + checkAuthentication: function() { + var metaTag = document.querySelector('meta[name="current-user"]'); + return metaTag && metaTag.getAttribute('content'); + }, + + getCurrentUserId: function() { + var metaTag = document.querySelector('meta[name="current-user"]'); + return metaTag ? metaTag.getAttribute('content') : null; + }, + + loadServerThemePreference: function(callback) { + var userId = this.getCurrentUserId(); + if (!userId) { + callback(this.getSystemTheme()); + return; + } + + var self = this; + fetch('/accounts/' + userId + '/theme_preference.json', { + method: 'GET', + credentials: 'same-origin', + headers: { + 'Accept': 'application/json' + } + }) + .then(function(response) { + if (!response.ok) { + return callback(self.getSystemTheme()); + } + return response.json(); + }) + .then(function(data) { + if (data && data.theme_preference) { + callback(data.theme_preference); + } else { + callback(self.getSystemTheme()); + } + }) + .catch(function(error) { + callback(self.getSystemTheme()); + }); + }, + + saveServerThemePreference: function(theme) { + if (!this.isAuthenticated) { + return; + } + + var userId = this.getCurrentUserId(); + if (!userId) { + return; + } + + fetch('/accounts/' + userId + '/set_theme_preference', { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': this.getCsrfToken() + }, + body: JSON.stringify({ theme: theme }) + }) + .then(function(response) { + return response.json(); + }) + .catch(function(error) { + // Silent fail - cookie already set + }); + }, + + getCsrfToken: function() { + var token = document.querySelector('meta[name="csrf-token"]'); + return token ? token.getAttribute('content') : ''; + }, + + getCookie: function(name) { + var nameEQ = name + '='; + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i].trim(); + if (cookie.indexOf(nameEQ) === 0) { + return cookie.substring(nameEQ.length); + } + } + return null; + }, + + setCookie: function(name, value, days) { + var expires = ''; + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = '; expires=' + date.toUTCString(); + } + document.cookie = name + '=' + value + expires + '; path=/; SameSite=Lax'; + }, + + getSystemTheme: function() { + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + return 'dark'; + } + return 'light'; + }, + + getSavedTheme: function() { + var cookieTheme = this.getCookie(this.COOKIE_NAME); + if (cookieTheme && (cookieTheme === 'light' || cookieTheme === 'dark')) { + return cookieTheme; + } + return this.getSystemTheme(); + }, + + applyTheme: function(theme) { + var html = document.documentElement; + var moonIcon = document.getElementById('theme-icon-moon'); + var sunIcon = document.getElementById('theme-icon-sun'); + + if (theme === 'dark') { + html.classList.add('dark'); + if (moonIcon) moonIcon.classList.add('hidden'); + if (sunIcon) sunIcon.classList.remove('hidden'); + } else { + html.classList.remove('dark'); + if (moonIcon) moonIcon.classList.remove('hidden'); + if (sunIcon) sunIcon.classList.add('hidden'); + } + + this.setCookie(this.COOKIE_NAME, theme, this.COOKIE_DAYS); + }, + + toggleTheme: function() { + var currentTheme = this.getSavedTheme(); + var newTheme = currentTheme === 'light' ? 'dark' : 'light'; + this.applyTheme(newTheme); + if (this.isAuthenticated) { + this.saveServerThemePreference(newTheme); + } + }, + + bindEvents: function() { + var self = this; + var themeToggleBtn = document.getElementById('theme-toggle'); + + if (themeToggleBtn) { + themeToggleBtn.onclick = function(e) { + e.preventDefault(); + self.toggleTheme(); + return false; + }; + } + } +}; + +// Initialize on DOM ready +$(document).on('page:change', function() { + ThemeToggle.init(); +}); + +// Also initialize immediately if DOM is already loaded +$(document).ready(function() { + ThemeToggle.init(); +}); diff --git a/app/assets/javascripts/type_ahead.js.coffee b/app/assets/javascripts/type_ahead.js.coffee index b6ea3acb2..1d56c9e15 100644 --- a/app/assets/javascripts/type_ahead.js.coffee +++ b/app/assets/javascripts/type_ahead.js.coffee @@ -46,10 +46,12 @@ class App.TypeAhead class App.ChosenSelect constructor: () -> + $(document).on 'chosen:ready', (event, params) -> + $(params.chosen.container).find('.chosen-search-input').attr('aria-label', 'Search') $('.chzn-select').chosen() $('#sort_by .chzn-search').hide() $('.nav-select-container .chzn-search').show() - $('.value-select').chosen() + $('.value-select').not('.chzn-select').chosen() $(document).ready -> new App.ChosenSelect diff --git a/app/assets/javascripts/upload.js.coffee b/app/assets/javascripts/upload.js.coffee index a84e2d920..efde68f8b 100644 --- a/app/assets/javascripts/upload.js.coffee +++ b/app/assets/javascripts/upload.js.coffee @@ -13,6 +13,8 @@ FileUpload = $('input[type="submit"]').removeAttr('disabled') true + $('.ace-file-btn').attr('aria-label', 'Choose file') + $('.new_file_upload').on 'change', () -> if this.files[0].size > $(this).data('max_size') $('input[type="submit"]').attr('disabled', 'disabled') diff --git a/app/assets/stylesheets/account.sass b/app/assets/stylesheets/account.sass index cbdaa6d07..a7e7ab27b 100644 --- a/app/assets/stylesheets/account.sass +++ b/app/assets/stylesheets/account.sass @@ -1,88 +1,509 @@ -#accounts_index_page, #people_index_page - .account_details - width: 220px - margin-left: 15px +// ── People page max-width container ────────────────────────────────────────── +.people-page-container + max-width: 1280px + margin: 0 auto + padding: 32px 16px 0 + +.people-page-header + display: flex + flex-direction: column + gap: 16px + margin-bottom: 32px + @media only all and (min-width: 640px) + flex-direction: row + align-items: center + justify-content: space-between + .people-page-title + margin: 0 + +// ── People & Accounts page headings ────────────────────────────────────────── +.people-page-title + font-size: 24px + font-weight: 700 + color: #111827 + margin: 0 + @media only all and (min-width: 640px) + font-size: 30px + @media only all and (min-width: 1024px) + font-size: 36px + +.people-pagination + margin-bottom: 32px + +.people-section-title + font-size: 20px !important + font-weight: 700 !important + color: #111827 + margin: 0 0 16px + line-height: 1.3 !important + @media only all and (min-width: 640px) + font-size: 24px !important + margin-bottom: 24px + +.accounts-back-link + display: inline-flex + align-items: center + gap: 6px + color: #4b5563 + text-decoration: none + font-size: 14px + margin-bottom: 24px + transition: color 0.2s + &:hover + color: #111827 + text-decoration: none + +.people-more-btn + display: block + width: 100% + background-color: #5A2A82 + color: #ffffff + border: none + padding: 8px 16px + font-weight: 600 + border-radius: 4px + font-size: 14px + text-align: center + text-decoration: none + margin-top: 12px + margin-bottom: 12px + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23) + transition: background-color 0.2s, box-shadow 0.2s + &:hover + background-color: #4A1A72 + color: #ffffff + text-decoration: none + @media only all and (min-width: 640px) + display: inline-block + width: auto + padding: 8px 24px + font-size: 16px + margin-top: 12px + +// ── People Card ─────────────────────────────────────────────────────────────── +.people-card + background: #ffffff + border-radius: 16px + border: 1px solid rgba(229, 231, 235, 0.8) + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + transition: border-color 0.3s, box-shadow 0.3s + margin-bottom: 16px + overflow: hidden + cursor: pointer + &:hover + border-color: #5A2A82 + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23) + +.people-card__body + padding: 16px + +// Card row — mobile: column, desktop: row +.people-card__row + display: flex + flex-direction: column + gap: 12px + +// Avatar + info wrapper — on desktop becomes display:contents +// so avatar & info become direct flex children of .people-card__row +.people-card__avatar-info + display: flex + align-items: flex-start + gap: 16px + + img, .gravatar + width: 80px + height: 80px + border-radius: 4px + object-fit: cover + flex-shrink: 0 + +.people-card__info + flex: 1 + min-width: 0 + +.people-card__name + font-size: 20px + font-weight: 600 + margin: 0 0 4px + line-height: 1.4 + a + color: #5a2a82 !important + text-decoration: none + &:hover + text-decoration: underline + +.people-card__meta + font-size: 13px + color: #6b7280 + margin: 2px 0 + a + color: #6b7280 + text-decoration: none + &:hover + color: #5A2A82 + text-decoration: underline + +// Commits — mobile: top border, desktop: left border +.people-card__commits + display: flex + flex-direction: column + align-items: center + justify-content: center + padding: 8px 0 + border-top: 1px solid #e5e7eb + +.people-card__commits-count + font-size: 30px + font-weight: 700 + color: #111827 + line-height: 1.1 + text-align: center + +.people-card__commits-label + font-size: 11px + color: #6b7280 + text-transform: uppercase + letter-spacing: 0.05em + text-align: center + margin-top: 4px + a + color: #6b7280 + text-decoration: none + font-size: 11px + &:hover + color: #5A2A82 + +// Activity — languages + projects +.people-card__activity + flex: 1 + min-width: 0 + display: flex + flex-direction: column + gap: 15px + +.people-card__row-langs, +.people-card__row-projects + display: flex + align-items: flex-start + gap: 8px + flex-wrap: wrap + +.people-card__label + font-size: 12px + color: #6b7280 + white-space: nowrap + padding-top: 2px + flex-shrink: 0 + +.people-card__lang-tags + display: flex + align-items: center + flex-wrap: wrap + gap: 6px + +.people-card__lang-primary + display: inline-block + padding: 2px 10px + background: linear-gradient(to right, #5A2A82, #7F3FA0) + color: #ffffff + font-size: 12px + font-weight: 600 + border-radius: 4px + a + color: #ffffff !important + text-decoration: none + &:hover + text-decoration: underline + +.people-card__lang-secondary + font-size: 13px + color: #374151 + a + color: #374151 + text-decoration: none + &:hover + color: #5A2A82 + +.people-card__no-lang + font-size: 13px + color: #6b7280 + font-style: italic + +.people-card__project-list + display: flex + align-items: center + flex-wrap: wrap + gap: 8px + +.people-card__project-item + display: flex + align-items: center + gap: 4px + img, .people-card__project-icon + width: 20px + height: 20px + border-radius: 3px + flex-shrink: 0 + +.people-card__project-name + font-size: 13px + color: #5A2A82 + text-decoration: none + &:hover + text-decoration: underline + +.people-card__other-links + font-size: 12px + margin-top: 2px + .people-card__link + color: #5A2A82 + text-decoration: none + &:hover + text-decoration: underline + .people-card__link-dot + margin: 0 2px + &::before + content: '•' + color: #d1d5db + +.people-card__no-commits + font-size: 14px + color: #6b7280 + font-style: italic + text-align: center + padding: 20px 0 + +// Kudos — mobile: top border row, desktop: column no border +.people-card__kudos + display: flex + align-items: center + justify-content: center + gap: 12px + padding-top: 8px + border-top: 1px solid #e5e7eb + flex-shrink: 0 + +.people-card__kudos-rank + font-size: 28px + font-weight: 700 + color: #111827 + text-align: center + +.people-card__kudos-badge + display: flex + align-items: center + +// ── Unclaimed Card ──────────────────────────────────────────────────────────── +.unclaimed-card__header + display: flex + align-items: center + justify-content: space-between + gap: 12px + margin-bottom: 16px + flex-wrap: wrap + +.unclaimed-card__name + font-size: 20px + font-weight: 600 + color: #5A2A82 + margin: 0 + line-height: 1.4 + a + color: #5A2A82 + text-decoration: none + &:hover + text-decoration: underline + +.unclaimed-card__btn + background-color: #5A2A82 !important + color: #ffffff !important + border: none !important + padding: 6px 14px !important + font-weight: 600 !important + height: auto !important + line-height: normal !important + border-radius: 6px !important + font-size: 13px !important + white-space: nowrap + transition: background-color 0.2s + &:hover + background-color: #4A1A72 !important + color: #ffffff !important + +// Ensure inputs and buttons with this class inherit font family and weight +input.unclaimed-card__btn, +button.unclaimed-card__btn + font-weight: 600 !important + font-family: inherit !important + -webkit-font-smoothing: antialiased !important + height: auto !important + line-height: normal !important + +.unclaimed-card__grid + display: grid + grid-template-columns: repeat(2, 1fr) + gap: 16px + +.unclaimed-card__item + position: relative + + // Modern tile layout to match OpenHub design (image + absolute commit badge) + display: flex + flex-direction: column + align-items: center + gap: 8px + min-width: 0 + padding: 8px + +.unclaimed-card__more + grid-column: 1 / -1 + display: flex + align-items: center + justify-content: center + a + font-size: 13px + color: #5A2A82 + text-decoration: none + &:hover + text-decoration: underline + +// Unclaimed item inner elements: logo and commit badge +.unclaimed-card__item + .logo position: relative - height: 128px - .commits_summary - @include commits-summary-background-color - width: 470px - margin: 5px 0px - height: 118px - .in - padding: 49px 2px - .project_summary - padding: 5px 10px 0px 7px - width: auto - min-height: 99px - div - height: 23px - .project_name - @include projects-summary-projects-name-background-color - color: white - border: 1px dashed white + img, p + width: 48px !important + height: 48px !important + border-radius: 8px + object-fit: cover + border: 1px solid #e5e7eb !important + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) + @media only all and (min-width: 640px) + width: 64px !important + height: 64px !important + + p + // placeholder text inside logo (when no image available) + display: flex + align-items: center + justify-content: center + margin: 0 + font-size: 16px + font-weight: 600 + color: #6b7280 + background: #f3f4f6 + border-radius: 8px + @media only all and (min-width: 640px) font-size: 18px - line-height: 24px - width: 24px - height: 24px - text-align: center - float: left - margin-bottom: 0 - margin-top: 0 - margin-right: 2px - .language_summary - padding: 5px 7px 5px 9px - width: 129px - .language_name - width: 113px - margin-bottom: 5px - height: 28px - line-height: 28px - span - padding-left: 8px - a - color: inherit - .other_language - color: white - font-size: 10px - white-space: nowrap - line-height: 10px - margin-bottom: 0 - a - @include site-link-color - .more_commits_padding - padding-left: 10px - .no_commits - text-align: center - padding: 50px - font-size: 15px - color: black - .member_info + + .commits + // container for commit badge + label; keep in document flow so label sits below logo + font-size: 12px + display: inline-flex + flex-direction: column + align-items: center + justify-content: center + + a + color: #6a7282 !important + text-transform: uppercase + line-height: 1.3 + letter-spacing: 0.3px + + .logo .commit-count + // absolute badge overlayed on the project logo (positioned relative to .logo) position: absolute - bottom: 0px - left: 0px - .kudo_rank - margin-top: 0px - font-size: 30px - text-align: center - padding: 42px 5px 0px 5px - font-weight: 100 - width: 35px - color: #183867 - .commits_count - font-size: 27px - line-height: 30px - display: inline-block - padding: 31px 20px 20px + top: -6px + right: -6px + font-size: 12px + display: inline-flex + align-items: center + justify-content: center + background: linear-gradient(to right, #5A2A82, #7F3FA0) + color: #ffffff + padding: 2px 6px + border-radius: 4px + font-weight: 700 + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) + @media only all and (min-width: 640px) + top: -8px + right: -8px + padding: 4px 8px + + .project_link + font-size: 12px + line-height: 1.4 text-align: center - width: 115px - div - font-size: 12px - text-align: center !important + width: 100% + overflow-wrap: break-word + word-break: break-word + cursor: pointer + @media only all and (min-width: 640px) + font-size: 14px + a + color: #5A2A82 + &:hover + text-decoration: underline + +// ── Desktop overrides (sm: 576px+) ─────────────────────────────────────────── +@media only all and (min-width: 576px) + .people-card__body + padding: 24px + + .people-card__row + flex-direction: row + align-items: flex-start + gap: 24px + + // Make avatar+info transparent so children become direct row flex items + .people-card__avatar-info + display: contents + img, .gravatar + width: 96px + height: 96px + border-radius: 4px + + // Commits: remove top border, add left border + .people-card__commits + border-top: none + border-left: 1px solid #e5e7eb + padding: 16px 24px + + .people-card__commits-count + font-size: 40px + + // Kudos: remove top border, become a column + .people-card__kudos + flex-direction: column + border-top: none + padding-top: 0 + gap: 4px + + // Unclaimed grid: flex wrap on desktop + .unclaimed-card__grid + display: flex + flex-wrap: wrap + gap: 24px + +#account-settings-page + max-width: 1024px + margin: 0 auto + padding: 0 24px 32px + + @media (max-width: 639px) + padding: 0 16px 24px + + .settings-grid + @media (min-width: 640px) + grid-template-columns: repeat(2, 1fr) + @media (min-width: 1024px) + grid-template-columns: repeat(2, 1fr) + +// ── Account settings (unchanged) ───────────────────────────────────────────── #analysis_timestamp - @include analysis-timestamp-color margin-top: 0px + margin-left: 32px .account_settings_options margin-left: 20px @@ -127,6 +548,7 @@ .settings_specifications width: 380px length: 88px + .verification-buttons-container width: 100% margin-left: auto @@ -142,94 +564,35 @@ .grecaptcha-badge display: none -@media only all and (min-width: 768px) and (max-width: 1024px) - #accounts_index_page - h1 - font-size: 24px - margin-bottom: 0 - margin-top: 10px - h3 - font-size: 18px - margin-bottom: 0 - margin-top: 10px - .account_details - width: 142px - margin-left: 5px - .commits_summary - width: 379px - .commits_count - width: auto - font-size: 15px - padding: 31px 0px 12px - line-height: 20px - .well - min-height: 142px - .language_summary - width: 100px !important - .language_name - width: 85px !important - .kudo_rank - font-size: 18px - padding: 0 - font-weight: 500 - margin-top: 30px !important - width: auto - .kudo_badge - margin-top: 28px !important - padding: 0 - #search-dingus label.paginate - font-size: 10px !important - label - font-size: 10px - #search-dingus button.btn - font-size: 10px -@media only all and (min-width: 320px) and (max-width: 480px) - #accounts_index_page - h1 - font-size: 18px - margin-bottom: 0 - margin-top: 10px - h3 - font-size: 12px - margin-bottom: 0 - margin-top: 10px - .account_details - width: auto - .commits_summary - width: auto - .commits_count - width: auto - font-size: 15px - padding: 31px 0px 12px - line-height: 20px - .kudo_rank - font-size: 24px - padding: 0 - margin-top: 35px - .kudo_badge - margin-top: 28px !important - padding: 0 - .language_name - width: 90px !important - .unclaimed_committers_box .header_row h4 - font-size: 12px - .well - min-height: 142px - .unclaimed_committers_box .header_row .btn-mini - font-size: 8px - .language_summary - width: 100px !important - #more_account - font-size: 10px - #more_unclaimed - font-size: 10px - #search-dingus label.paginate - font-size: 8px !important - label - font-size: 8px - #search-dingus button.btn - font-size: 8px - #search-dingus input - width: auto - #search-dingus select - width: auto + +// Licenses index page — same container/header pattern as people page +.people-page-header__action + display: flex + align-items: center + +.licenses-list + display: flex + flex-direction: column + gap: 8px + margin-top: 8px + margin-bottom: 10px + + &__item + padding: 14px 20px !important + margin-bottom: 0 !important + + &__link + font-size: 15px + font-weight: 500 + color: #5A2A82 + text-decoration: none + + &:hover + color: #4a1f6e + text-decoration: underline + + html.dark & + color: #ffb91a + + &:hover + color: #ffcc4d diff --git a/app/assets/stylesheets/accounts/edit.sass b/app/assets/stylesheets/accounts/edit.sass index 985538472..4205c2c36 100644 --- a/app/assets/stylesheets/accounts/edit.sass +++ b/app/assets/stylesheets/accounts/edit.sass @@ -2,12 +2,541 @@ #settings a margin: 0 0 0.2em 0 +#accounts_edits + max-width: 1024px + margin: 0 auto + padding: 0 24px 32px + + @media (max-width: 639px) + padding: 0 16px 24px + + // Remove inherited margin-left so content aligns with the header + > .margin_left_15 + margin-left: 0 !important + margin-right: 0 !important + + .settings-page-header + margin-top: 16px + + .account-basics-sep + color: #9ca3af + font-weight: 400 + +// ── Form grid ────────────────────────────────────────────────────────────────── +.edit-form-grid + display: grid + grid-template-columns: 1fr + gap: 20px + margin-bottom: 20px + + @media (min-width: 768px) + grid-template-columns: 1fr 1fr + gap: 24px + +.edit-form-col + display: flex + flex-direction: column + gap: 20px + +// ── Section card ─────────────────────────────────────────────────────────────── +.edit-section + background: #ffffff + border-radius: 16px + border: 1px solid #e5e7eb + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06) + padding: 20px 24px + + html.dark & + background: #2D1548 + border-color: rgba(90, 42, 130, 0.35) + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.3) + + &.edit-section--full + grid-column: 1 / -1 + +.edit-section-header + display: flex + align-items: center + gap: 10px + margin-bottom: 18px + padding-bottom: 14px + border-bottom: 1px solid #f3f4f6 + + html.dark & + border-bottom-color: rgba(255, 255, 255, 0.08) + +.edit-section-icon + width: 32px + height: 32px + min-width: 32px + border-radius: 10px + background: linear-gradient(135deg, #5A2A82, #7B3FB5) + display: flex + align-items: center + justify-content: center + box-shadow: 0 2px 6px rgba(90, 42, 130, 0.3) + i + color: #ffffff + font-size: 14px + margin: 0 + line-height: 1 + +.edit-section-title + font-size: 14px !important + font-weight: 700 !important + color: #111827 !important + margin: 0 !important + letter-spacing: 0.01em + + html.dark & + color: #ffffff !important + +// ── Field groups ─────────────────────────────────────────────────────────────── +.edit-field-group + margin-bottom: 16px + + &:last-child + margin-bottom: 0 + +.edit-field-label + display: block + font-size: 13px + font-weight: 600 + color: #374151 + margin-bottom: 6px + + html.dark & + color: #d1d5db + +.edit-field-input-wrapper + position: relative + display: block + + .field_with_errors + display: block + + .edit-field-input + border-color: #ef4444 + background: #fff5f5 + + html.dark & + background: rgba(239, 68, 68, 0.1) + +.edit-field-icon + position: absolute + left: 14px + top: 50% + transform: translateY(-50%) + font-size: 14px + color: #666666 + pointer-events: none + z-index: 2 + + html.dark & + color: #a0a6b0 + +input.edit-field-input, +textarea.edit-field-input + display: block !important + width: 100% !important + padding: 12px 16px 12px 42px !important + border: 1px solid #e5e7eb !important + border-radius: 12px !important + -webkit-border-radius: 12px !important + -moz-border-radius: 12px !important + font-size: 14px !important + font-weight: 400 !important + background: #f9fafb !important + color: #111827 !important + line-height: 1.5 !important + height: auto !important + box-shadow: none !important + box-sizing: border-box !important + -webkit-appearance: none !important + appearance: none !important + outline: none !important + transition: border-color 0.2s, box-shadow 0.2s, background 0.2s + + &::placeholder + color: #767676 !important + font-weight: 400 !important + + &:focus, + &:focus-visible + border-color: #5A2A82 !important + box-shadow: 0 0 0 2px rgba(90, 42, 130, 0.25) !important + background: #ffffff !important + outline: none !important + + html.dark & + background: #1D0631 !important + border-color: rgba(90, 42, 130, 0.5) !important + color: #ffffff !important + + &::placeholder + color: #4b5563 !important + + &:focus, + &:focus-visible + border-color: #ffb91a !important + box-shadow: 0 0 0 2px rgba(255, 185, 26, 0.25) !important + background: #180529 !important + +.edit-field-textarea + display: block !important + width: 100% !important + padding: 12px 16px !important + border: 1px solid #e5e7eb !important + border-radius: 12px !important + font-size: 14px !important + background: #f9fafb !important + color: #111827 !important + line-height: 1.6 !important + resize: vertical + box-sizing: border-box !important + -webkit-appearance: none !important + appearance: none !important + outline: none !important + transition: border-color 0.2s, box-shadow 0.2s, background 0.2s + + &::placeholder + color: #767676 !important + + &:focus, + &:focus-visible + border-color: #5A2A82 !important + box-shadow: 0 0 0 2px rgba(90, 42, 130, 0.25) !important + background: #ffffff !important + outline: none !important + + html.dark & + background: #1D0631 !important + border-color: rgba(90, 42, 130, 0.5) !important + color: #ffffff !important + + &::placeholder + color: #4b5563 !important + + &:focus, + &:focus-visible + border-color: #ffb91a !important + box-shadow: 0 0 0 2px rgba(255, 185, 26, 0.25) !important + background: #180529 !important + +.edit-field-hint + font-size: 11px + color: #666666 + margin: 6px 0 0 + line-height: 1.5 + + html.dark & + color: #a0a6b0 + + a + color: #5A2A82 + text-decoration: underline + html.dark & + color: #ffb91a + +.edit-field-error + font-size: 12px + color: #ef4444 + margin: 6px 0 0 + +.edit-clear-link + display: inline-block + font-size: 12px + color: #5A2A82 + text-decoration: underline + margin-top: 6px + cursor: pointer + + html.dark & + color: #ffb91a + +// ── Map ──────────────────────────────────────────────────────────────────────── +#map + width: 100% + height: 200px + border-radius: 10px + overflow: hidden + margin-top: 12px + border: 1.5px solid #e5e7eb + + html.dark & + border-color: rgba(90, 42, 130, 0.4) + +// ── Affiliation chosen override ──────────────────────────────────────────────── #account-edit-form - .chzn-container, .chosen p - width: 347px !important + #value_select + .chosen-container-single .chosen-single + border: 1px solid #e5e7eb !important + border-radius: 12px !important + -webkit-border-radius: 12px !important + background: #f9fafb !important + background-image: none !important + height: auto !important + padding: 12px 16px !important + box-shadow: none !important + color: #111827 !important + line-height: 1.5 !important + font-size: 14px !important + &:hover + border-color: #d1d5db !important + .chosen-container-active > .chosen-single, + .chosen-container-active.chosen-with-drop .chosen-single + border-color: #5A2A82 !important + box-shadow: 0 0 0 2px rgba(90, 42, 130, 0.25) !important + .chosen-container .chosen-drop + border-radius: 0 0 12px 12px !important + border-left: 1px solid #5A2A82 !important + border-right: 1px solid #5A2A82 !important + border-bottom: 1px solid #5A2A82 !important + border-top: none !important + html.dark & + .chosen-container-single .chosen-single + background: #1D0631 !important + border-color: rgba(90, 42, 130, 0.5) !important + color: #ffffff !important + &:hover + border-color: #6b7280 !important + .chosen-container-active > .chosen-single, + .chosen-container-active.chosen-with-drop .chosen-single + border-color: #ffb91a !important + box-shadow: 0 0 0 2px rgba(255, 185, 26, 0.25) !important + .chosen-container .chosen-drop + border-left: 1px solid #ffb91a !important + border-right: 1px solid #ffb91a !important + border-bottom: 1px solid #ffb91a !important + +// ── Action buttons ───────────────────────────────────────────────────────────── +.edit-form-actions + display: flex + align-items: center + gap: 12px + padding-top: 8px + margin-bottom: 32px + +.edit-save-btn + display: inline-flex !important + align-items: center + padding: 11px 28px !important + background: linear-gradient(135deg, #5A2A82 0%, #7B3FB5 100%) !important + color: #ffffff !important + border: none !important + border-radius: 10px !important + font-size: 14px !important + font-weight: 700 !important + cursor: pointer !important + box-shadow: 0 2px 8px rgba(90, 42, 130, 0.35) !important + transition: background 0.15s, box-shadow 0.15s + -webkit-appearance: none !important + appearance: none !important + + &:hover + background: linear-gradient(135deg, #4a1f6e 0%, #6B35A0 100%) !important + box-shadow: 0 4px 14px rgba(90, 42, 130, 0.45) !important + color: #ffffff !important + +.edit-delete-btn + display: inline-flex !important + align-items: center + padding: 11px 20px !important + background: transparent !important + color: #dc2626 !important + border: 1.5px solid #fca5a5 !important + border-radius: 10px !important + font-size: 14px !important + font-weight: 600 !important + cursor: pointer !important + text-decoration: none !important + transition: background 0.15s, border-color 0.15s + + &:hover + background: #fef2f2 !important + border-color: #dc2626 !important + color: #dc2626 !important + text-decoration: none !important + + html.dark & + color: #f87171 !important + border-color: rgba(248, 113, 113, 0.4) !important + + &:hover + background: rgba(220, 38, 38, 0.1) !important + border-color: #f87171 !important + +@media (max-width: 767px) + #accounts_edits + .edit-form-grid + grid-template-columns: 1fr !important + + .edit-form-actions + flex-direction: column + align-items: stretch + + .edit-save-btn, + .edit-delete-btn + width: 100% !important + justify-content: center + +// ── About Account Basics card ────────────────────────────────────────────────── +.about-account-basics-card + max-width: 1024px + margin: 0 auto 32px + padding: 0 24px + + @media (max-width: 639px) + padding: 0 16px + + background: #fffbeb + border-radius: 16px + border: 1px solid rgba(251, 191, 36, 0.35) + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06) + overflow: hidden + + html.dark & + background: rgba(120, 53, 15, 0.12) + border-color: rgba(251, 191, 36, 0.25) + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.3) + + .card-header + display: flex + align-items: center + justify-content: space-between + padding: 14px 20px + cursor: pointer + user-select: none + transition: background-color 0.15s + + &:hover + background-color: rgba(251, 191, 36, 0.08) + + .card-header-left + display: flex + align-items: center + gap: 12px + flex: 1 + + .card-header-icon + width: 32px + height: 32px + min-width: 32px + border-radius: 10px + background: linear-gradient(135deg, #f59e0b, #d97706) + display: flex + align-items: center + justify-content: center + box-shadow: 0 2px 6px rgba(217, 119, 6, 0.3) + i + color: #ffffff + font-size: 14px + line-height: 1 + + .card-header-text + flex: 1 + + .card-title + margin: 0 0 1px + font-size: 14px !important + font-weight: 700 !important + color: #92400e !important + + html.dark & + color: #fbbf24 !important + + .card-subtitle + margin: 0 + font-size: 11px + color: #b45309 + line-height: 1.3 + + html.dark & + color: #d97706 + + .expand-toggle + background: none + border: none + padding: 4px 8px + cursor: pointer + color: #b45309 + transition: transform 0.2s + + html.dark & + color: #d97706 + + i + font-size: 13px + + .card-content + display: none + padding: 4px 20px 20px + + &.expanded + .expand-toggle + transform: rotate(180deg) + + .card-content + display: block + + .tips-grid + display: grid + grid-template-columns: 1fr + gap: 12px + + @media (min-width: 640px) + grid-template-columns: 1fr 1fr + gap: 14px + + .tip-item + display: flex + align-items: flex-start + gap: 10px + padding: 10px 12px + background: rgba(255, 255, 255, 0.7) + border-radius: 10px + border: 1px solid rgba(251, 191, 36, 0.2) + + html.dark & + background: rgba(255, 255, 255, 0.04) + border-color: rgba(251, 191, 36, 0.15) + + .tip-icon + width: 26px + height: 26px + min-width: 26px + border-radius: 8px + background: rgba(217, 119, 6, 0.12) + display: flex + align-items: center + justify-content: center + margin-top: 1px + + html.dark & + background: rgba(251, 191, 36, 0.12) + + i + font-size: 11px + color: #d97706 + + html.dark & + color: #fbbf24 + + .tip-body + flex: 1 + + .tip-text + margin: 0 + font-size: 12px + color: #555555 + line-height: 1.6 + + html.dark & + color: #d1d5db - #location - width: 85% + strong + color: #374151 + font-weight: 600 - .edit-description - width: 90% + html.dark & + color: #ffffff diff --git a/app/assets/stylesheets/accounts/languages.sass b/app/assets/stylesheets/accounts/languages.sass index f97f6bab5..2c652e471 100644 --- a/app/assets/stylesheets/accounts/languages.sass +++ b/app/assets/stylesheets/accounts/languages.sass @@ -1,3 +1,11 @@ +#accounts-languages-page + max-width: 1024px + margin: 0 auto + padding: 0 24px 32px + + @media (max-width: 639px) + padding: 0 16px 24px + #language_detail .lang_bg padding-top: 8px @@ -17,10 +25,45 @@ .chzn-select, .chzn-container :margin-top 2px +@media (max-width: 767px) + #language_detail + .table-responsive + border: none + table + font-size: 12px + td, th + padding: 4px 6px + white-space: nowrap + // Hide empty spacer column + tbody tr td:nth-child(7), + thead tr:first-child th:nth-child(6) + display: none + + .margin_top_10 + h2.col-md-2 + width: 100% !important + float: none !important + margin-bottom: 6px !important + + .pull-right + float: none !important + display: block !important + margin-bottom: 6px !important + + #sort_by.languages + float: none !important + margin-top: 0 !important + display: block !important + width: 100% !important + + .chzn-container, + select.chzn-select + width: 100% !important + #languages_index_page .projects background-color: #183867 !important .contributors background-color: #237b46 !important .commits - background-color: #8f908f !important \ No newline at end of file + background-color: #8f908f !important diff --git a/app/assets/stylesheets/accounts/new.sass b/app/assets/stylesheets/accounts/new.sass index 57b211c05..738f5df28 100644 --- a/app/assets/stylesheets/accounts/new.sass +++ b/app/assets/stylesheets/accounts/new.sass @@ -1,76 +1,645 @@ -@media(min-width: 320px) and (max-width: 480px) - #welcome-message-container - padding: 0px !important - margin-top: 1rem !important - display: contents - h2 - font-size: 18px !important - font-weight: 500 !important - #profile-description-1 - margin-top: -40px - #sign-up-git - margin-left: -30px - margin-bottom: 15px - #sign-up-email - margin-right: 170px +// ── Remove page_contents horizontal padding on sign-up / sign-in pages ──────── +#page-contents:has(.signup-page) + margin-left: 0 !important + margin-right: 0 !important + +// ── Signup page full layout ──────────────────────────────────────────────────── +.signup-page + display: flex + flex-direction: column + min-height: calc(100vh - 72px) + background: #ffffff + + @media (min-width: 768px) + flex-direction: row + +// ── Left panel ───────────────────────────────────────────────────────────────── +.signup-left-panel + position: relative + overflow: hidden + background: linear-gradient(135deg, #0D0019 0%, #1D0631 50%, #5A2A82 100%) + display: flex + flex-direction: column + justify-content: space-between + padding: 32px 24px + min-height: 280px + + @media (min-width: 768px) + width: 42% + flex-shrink: 0 + padding: 28px 20px + min-height: calc(100vh - 72px) + + @media (min-width: 1024px) + width: 46% + padding: 48px + + @media (min-width: 1280px) + padding: 64px + +.signup-orb + position: absolute + border-radius: 50% + pointer-events: none + +.signup-orb--top + top: -96px + right: -96px + width: 320px + height: 320px + background: rgba(255, 185, 26, 0.1) + filter: blur(48px) + +.signup-orb--bottom + bottom: -128px + left: -80px + width: 384px + height: 384px + background: rgba(90, 42, 130, 0.4) + filter: blur(48px) + +.signup-orb--mid + top: 33% + right: 0 + width: 256px + height: 256px + background: rgba(107, 33, 168, 0.2) + filter: blur(40px) + +.signup-left-content + position: relative + z-index: 1 + +.signup-badge + display: inline-flex + align-items: center + gap: 8px + padding: 6px 12px + background: rgba(255, 255, 255, 0.1) + backdrop-filter: blur(4px) + border: 1px solid rgba(255, 255, 255, 0.1) + border-radius: 999px + color: rgba(255, 255, 255, 0.8) + font-size: 12px + margin-bottom: 20px + +.signup-badge__dot + width: 6px + height: 6px + background: #ffb91a + border-radius: 50% + flex-shrink: 0 + +.signup-headline + font-size: 28px + font-weight: 700 + color: #ffffff + line-height: 1.2 + margin: 0 0 16px + letter-spacing: -0.02em + + @media (min-width: 768px) and (max-width: 1023px) + font-size: 26px margin-bottom: 10px - #sign-up-fields - display: contents - #sign-up-options - margin-top: -12px -.well .fa-exclamation-triangle - color: red -.well .fa-smile-o - color: green + @media (min-width: 1024px) + font-size: 36px -#sign-up-options - @include sign_buttons - .row-first - height: 100px + @media (min-width: 1280px) + font-size: 44px -#sign-up-fields +.signup-headline__accent + color: #ffb91a + +.signup-tagline + color: rgba(255, 255, 255, 0.7) + font-size: 14px + line-height: 1.7 + max-width: 340px + margin: 0 + + @media (min-width: 1024px) + font-size: 16px + +// ── Benefits list ────────────────────────────────────────────────────────────── +.signup-benefits + position: relative + z-index: 1 display: none + flex-direction: column + gap: 16px + margin-top: 32px + + @media (min-width: 768px) + display: flex + + @media (min-width: 768px) and (max-width: 1023px) + gap: 12px + margin-top: 20px + +.signup-benefit + display: flex + align-items: center + gap: 16px + +.signup-benefit__icon + width: 36px + height: 36px + border-radius: 12px + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) + color: #ffffff + font-size: 16px + +.signup-benefit__icon--amber + background: linear-gradient(135deg, #f59e0b, #f97316) + +.signup-benefit__icon--purple + background: linear-gradient(135deg, #5A2A82, #8b5cf6) + +.signup-benefit__icon--green + background: linear-gradient(135deg, #10b981, #0d9488) -#welcome-message-container - padding: 0px !important - margin-top: 1rem !important -#profile-description-2 - max-width: 400px +.signup-benefit__icon--blue + background: linear-gradient(135deg, #3b82f6, #6366f1) + +.signup-benefit__text + flex: 1 + +.signup-benefit__title + color: #ffffff + font-size: 13px + font-weight: 600 + margin: 0 0 2px + line-height: 1.3 + +.signup-benefit__desc + color: rgba(255, 255, 255, 0.55) + font-size: 11px + margin: 0 + line-height: 1.5 + +// ── Left footer disclaimer ───────────────────────────────────────────────────── +.signup-left-footer position: relative -#profile-description-2 .text-2 - position: relative; - display: block; -#profile-description-2 .show-more-2 - color: #1779dd; - position: relative; - font-size: 10px; - text-align: left; - cursor: pointer; -#profile-description-2 .show-more-2:hover - color: #1779dd; -#profile-description-2 .show-more-height-2 - height: 35px; - overflow: hidden; -#profile-description-1 - max-width: 400px; - position: relative; -#profile-description-1 .text-1 - position: relative; - display: block; -#profile-description-1 .show-more-1 - color: #1779dd; - position: relative; - font-size: 10px; - text-align: left; - cursor: pointer; -#profile-description-1 .show-more-1:hover - color: #1779dd; -#profile-description-1 .show-more-height-1 - height: 68px; - overflow: hidden; + z-index: 1 + display: none + margin-top: 40px + padding-top: 32px + border-top: 1px solid rgba(255, 255, 255, 0.1) + + @media (min-width: 768px) + display: block + + @media (min-width: 768px) and (max-width: 1023px) + margin-top: 20px + padding-top: 16px + +.signup-disclaimer + color: rgba(255, 255, 255, 0.45) + font-size: 11px + line-height: 1.7 + margin: 0 + +// ── Right panel ──────────────────────────────────────────────────────────────── +// Constrain #flash-msg to form card width inside auth right panels +.signup-right-panel + #flash-msg + width: 100% + max-width: 440px + margin: 0 0 16px + padding: 0 + +.signup-right-panel + flex: 1 + display: flex + flex-direction: column + align-items: center + justify-content: center + padding: 32px 16px + background: #f9fafb + + html.dark & + background: #1D0631 + + @media (min-width: 640px) + padding: 48px 24px + + @media (min-width: 768px) and (max-width: 1023px) + padding: 24px 16px + +// ── Form card ────────────────────────────────────────────────────────────────── +.signup-form-card + width: 100% + max-width: 440px + background: #ffffff + border-radius: 20px + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) + border: 1px solid #f3f4f6 + padding: 28px 24px + + html.dark & + background: #2D1548 + border-color: rgba(90, 42, 130, 0.4) + box-shadow: 0 4px 32px rgba(0, 0, 0, 0.5) + + @media (min-width: 640px) + padding: 36px 36px + + @media (min-width: 768px) and (max-width: 1023px) + padding: 24px 20px + +// ── GitHub button ────────────────────────────────────────────────────────────── +.btn-github + display: flex + align-items: center + justify-content: center + gap: 10px + width: 100% + padding: 14px 20px + background: #24292e + color: #ffffff !important + border-radius: 12px + font-size: 14px + font-weight: 600 + text-decoration: none !important + border: 1px solid transparent + transition: background 0.15s, box-shadow 0.15s + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) + margin-bottom: 8px + + &:hover + background: #1a1f24 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) + color: #ffffff !important + text-decoration: none !important + + html.dark & + background: #0D0019 + border-color: rgba(255, 255, 255, 0.1) + + html.dark &:hover + background: #000000 + + .fa + font-size: 18px + +.signup-github-note + font-size: 11px + color: #666666 + text-align: center + margin: 0 0 20px + line-height: 1.5 + + html.dark & + color: #a0a6b0 + +// ── Divider ──────────────────────────────────────────────────────────────────── +.signup-divider + display: flex + align-items: center + gap: 12px + margin-bottom: 24px + +.signup-divider__line + flex: 1 + height: 1px + background: #e5e7eb + + html.dark & + background: rgba(255, 255, 255, 0.1) + +.signup-divider__text + font-size: 11px + color: #666666 + font-weight: 500 + text-transform: uppercase + letter-spacing: 0.05em + white-space: nowrap + + html.dark & + color: #a0a6b0 + +// ── Form header ──────────────────────────────────────────────────────────────── +.signup-form-header + margin-bottom: 24px + +.signup-form-title + font-size: 22px !important + font-weight: 800 !important + color: #111827 !important + margin: 0 0 6px !important + letter-spacing: -0.02em + + html.dark & + color: #ffffff !important + +.signup-form-subtitle + font-size: 13px + color: #555555 + margin: 0 + + html.dark & + color: #a0a6b0 + +.signup-signin-link + color: #5A2A82 + font-weight: 600 + text-decoration: none + transition: color 0.15s + + &:hover + text-decoration: underline + color: #5A2A82 + + html.dark & + color: #ffb91a + + html.dark &:hover + color: #ffb91a + +// ── Email toggle button ──────────────────────────────────────────────────────── +.btn-email-signup + display: flex + align-items: center + justify-content: center + gap: 10px + width: 100% + padding: 14px 20px + background: #f9fafb + color: #1f2937 !important + border-radius: 12px + font-size: 14px + font-weight: 600 + text-decoration: none !important + border: 1px solid #e5e7eb + transition: background 0.15s, border-color 0.15s + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) + margin-bottom: 20px + + &:hover + background: #f3f4f6 + border-color: rgba(90, 42, 130, 0.4) + text-decoration: none !important + + html.dark & + background: #1D0631 + color: #ffffff !important + border-color: rgba(90, 42, 130, 0.6) + + html.dark &:hover + background: #180529 + + .fa + color: #5A2A82 + font-size: 16px + + html.dark & + color: #ffb91a + +// ── Form fields — scoped under .signup-form-card for specificity over Bootstrap ─ +#sign-up-fields + display: none + +.signup-form-card + fieldset + border: none !important + padding: 0 !important + margin: 0 !important + + legend + display: none !important + + // Field group + .signup-field-group + margin-bottom: 18px + + // Label + .signup-field-label + display: block + font-size: 13px + font-weight: 600 + color: #374151 + margin-bottom: 6px + line-height: 1.4 + + html.dark & + color: #d1d5db + + // Icon wrapper — position: relative so icon sits inside + .signup-field-input-wrapper + position: relative + display: block + + // field_with_errors adds a wrapper div on validation failure + .field_with_errors + display: block + + .signup-field-input + border-color: #ef4444 + background: #fff5f5 + + html.dark & + background: rgba(239, 68, 68, 0.1) + + // Left icon — vertically centered, non-interactive + .signup-field-icon + position: absolute + left: 14px + top: 50% + transform: translateY(-50%) + font-size: 14px + color: #666666 + pointer-events: none + z-index: 2 + + html.dark & + color: #a0a6b0 + + // Inputs — scoped to #account_form (ID = specificity 100) beats Bootstrap's .form-control (010) + // Also keep !important on visual-critical properties as belt-and-suspenders + input.signup-field-input + display: block !important + width: 100% !important + padding: 12px 16px 12px 42px !important + border: 1px solid #e5e7eb !important + border-radius: 12px !important + font-size: 14px !important + font-weight: 400 !important + background: #f9fafb !important + color: #111827 !important + line-height: 1.5 !important + height: auto !important + box-shadow: none !important + box-sizing: border-box !important + -webkit-appearance: none !important + appearance: none !important + outline: none !important + transition: border-color 0.2s, box-shadow 0.2s, background 0.2s + + &::placeholder + color: #767676 !important + font-weight: 400 !important + + &:focus, + &:focus-visible + border-color: #5A2A82 !important + box-shadow: 0 0 0 2px rgba(90, 42, 130, 0.25) !important + background: #ffffff !important + outline: none !important + + html.dark & + background: #1D0631 !important + border-color: rgba(90, 42, 130, 0.5) !important + color: #ffffff !important + + &::placeholder + color: #4b5563 !important + + &:focus, + &:focus-visible + border-color: #ffb91a !important + box-shadow: 0 0 0 2px rgba(255, 185, 26, 0.25) !important + background: #180529 !important + + // Error text + .signup-field-group + .error, span.error + display: block + font-size: 11px + color: #ef4444 + margin: 5px 0 0 + + // Hint text + .signup-field-hint + font-size: 11px + color: #666666 + margin: 5px 0 0 + line-height: 1.5 + + html.dark & + color: #a0a6b0 + + // Submit button row + .signup-field-actions + margin-top: 4px + margin-bottom: 20px + + // Submit button + input.signup-submit-btn + display: block !important + width: 100% !important + padding: 13px 20px !important + background: linear-gradient(135deg, #5A2A82 0%, #7B3FB5 100%) !important + color: #ffffff !important + border: none !important + border-radius: 12px !important + font-size: 14px !important + font-weight: 700 !important + cursor: pointer !important + text-align: center !important + letter-spacing: 0.02em + -webkit-appearance: none !important + appearance: none !important + box-shadow: 0 2px 8px rgba(90, 42, 130, 0.3) !important + transition: background 0.15s, box-shadow 0.15s + + &:hover, + &:focus + background: linear-gradient(135deg, #4a1f6e 0%, #6B35A0 100%) !important + box-shadow: 0 4px 16px rgba(90, 42, 130, 0.45) !important + color: #ffffff !important + outline: none !important + +// ── Please Note card ─────────────────────────────────────────────────────────── +.signup-note-card + border: 1px solid rgba(251, 191, 36, 0.4) + background: #fffbeb + border-radius: 12px + padding: 14px 16px + margin-bottom: 16px + + html.dark & + background: rgba(120, 53, 15, 0.15) + border-color: rgba(251, 191, 36, 0.3) + +.signup-note-card__inner + display: flex + align-items: flex-start + gap: 10px + +.signup-note-card__icon + color: #d97706 + font-size: 14px + margin-top: 2px + flex-shrink: 0 + + html.dark & + color: #fbbf24 + +.signup-note-card__body + flex: 1 + +.signup-note-card__title + font-size: 12px + font-weight: 600 + color: #92400e + margin: 0 0 4px + + html.dark & + color: #fcd34d + +.signup-note-card__text + font-size: 11px + color: #78350f + margin: 0 + line-height: 1.6 + + html.dark & + color: rgba(252, 211, 77, 0.8) + + a + color: #92400e + text-decoration: underline + + html.dark & + color: #fcd34d + +// ── Terms note ───────────────────────────────────────────────────────────────── +.signup-terms-note + font-size: 11px + color: #666666 + text-align: center + line-height: 1.6 + margin: 0 + + html.dark & + color: #a0a6b0 +.signup-terms-link + color: #555555 + text-decoration: underline + transition: color 0.15s + &:hover + color: #5A2A82 + html.dark &:hover + color: #ffb91a +// ── Flash / invite ───────────────────────────────────────────────────────────── +.signup-flash, +.signup-invite-msg + font-size: 13px + color: #374151 + margin-bottom: 12px + padding: 10px 14px + background: #f3f4f6 + border-radius: 8px + html.dark & + background: rgba(255, 255, 255, 0.05) + color: #d1d5db diff --git a/app/assets/stylesheets/accounts/pai.sass b/app/assets/stylesheets/accounts/pai.sass index 43a52fa12..50afdd84a 100644 --- a/app/assets/stylesheets/accounts/pai.sass +++ b/app/assets/stylesheets/accounts/pai.sass @@ -1,10 +1,5 @@ @import oh-styles -#project_header_activity_indicator - width: 88px - margin-top: 1.4rem - margin-left: 2rem - .thirtyfive_project_activity_text font-size: 11px text-align: center @@ -29,26 +24,28 @@ text-align: left margin-left: 19px -#i_use_this_container - margin-bottom: 0.8rem - .btn.iusethis - font-size: 13px - height: 30px - margin-right: 10px - - .use_count - padding: 0 10px - font-size: 24px - height: 32px - line-height: 36px - display: block +// Redesigned header activity indicator wrapper +.activity-indicator-wrapper + display: flex + flex-direction: column + align-items: center + justify-content: center + gap: 8px + min-width: 80px + + [class^='thirtyfive_project_activity_level_'] + position: static + top: auto + margin-left: 0 + transform: scale(1.25) + transform-origin: center center + + .thirtyfive_project_activity_text + margin: 0 + width: auto + line-height: 1.2 + font-size: 12px + font-weight: 600 + color: white text-align: center - margin-top: 2px - a - @include site-link-color - -#project_container - #i_use_this_container - padding: 0 - .btn.iusethis - font-size: 2rem + white-space: nowrap diff --git a/app/assets/stylesheets/accounts/show.sass b/app/assets/stylesheets/accounts/show.sass index ecedde452..af676e571 100644 --- a/app/assets/stylesheets/accounts/show.sass +++ b/app/assets/stylesheets/accounts/show.sass @@ -1,98 +1,1414 @@ +// ═════════════════════════════════════════════════════════════════════ +// ACCOUNT SHOW PAGE - FIGMA DESIGN STYLING +// Palette: #5A2A82 primary, #1D0631 dark bg, #2D1548 dark card, +// #c084fc dark accent, #ffb91a yellow, #f97316 lang badge +// ═════════════════════════════════════════════════════════════════════ + #accounts_show_page - color: black - margin-top: -5px + background-color: #f9fafb + color: #111827 + margin: 0 !important + padding: 0 0 32px + min-height: 100vh + transition: background-color 0.3s ease, color 0.3s ease + + html.dark & + background-color: #1D0631 + color: #e5e7eb + .link_no_underline &:hover text-decoration: none !important - .primary_language_bg - text-align: center - width: 128px - line-height: 20px - .stacked_projects - img - margin-bottom: 10px !important - padding: 3px - p - margin-bottom: 10px !important -.dev_history - padding-top: 1em + +// ═════════════════════════════════════════════════════════════════════ +// HERO HEADER — full-width white section, inner content max-width constrained +// ═════════════════════════════════════════════════════════════════════ +.account_header + background: #ffffff + border-bottom: 1px solid #e5e7eb + padding: 0 !important + margin: 0 0 24px !important + max-width: none !important + width: 100% + position: static + + html.dark & + background: #1D0631 + border-bottom-color: rgba(42, 18, 69, 1) + +// ─── Inner constrained container ───────────────────────────────────── +.account_header_inner + max-width: 1024px + margin: 0 auto + padding: 24px 16px 20px + display: flex + flex-direction: column + gap: 16px + + @media (min-width: 640px) + padding: 24px 24px 20px + +// ─── Back link row ──────────────────────────────────────────────────── +.header_back_row + margin-bottom: -4px + +.header-back-link + display: inline-flex + align-items: center + gap: 4px + font-size: 12px + font-weight: 500 + color: #6b7280 + text-decoration: none + transition: color 0.15s + + &:hover + color: #5A2A82 + text-decoration: none + + html.dark & + color: #9ca3af + + html.dark &:hover + color: #ffb91a + + i + font-size: 11px + +// ─── Header Flex Container (Avatar + Name content) ────────────────── +.header_flex_container + display: flex + gap: 16px + align-items: flex-start + width: 100% + + @media (min-width: 768px) + gap: 20px + +// ─── Avatar Section (80px mobile / 96px sm+, rounded-2xl) ────────────────────────── #account_icon - width: 130px - margin-bottom: 18px - margin-top: 15px - margin-left: -15px + width: 80px !important + height: 80px !important + margin: 0 !important + padding: 0 !important + position: relative + flex-shrink: 0 + border-radius: 16px + overflow: hidden + border: 2px solid rgba(90, 42, 130, 0.3) + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) + + html.dark & + border-color: rgba(192, 132, 252, 0.25) + a display: block + width: 100% + height: 100% + + img + width: 100% !important + height: 100% !important + object-fit: cover + display: block + padding: 0 !important + margin: 0 !important + + @media (min-width: 640px) + width: 96px !important + height: 96px !important + +// ─── Primary Language Badge (overlay on avatar — Figma: -bottom-2 -right-2) ───── +.primary_language_bg + position: absolute + bottom: -8px + right: -8px + width: 24px !important + height: 24px !important + border-radius: 6px + font-size: 10px !important + font-weight: 700 + border: 2px solid #ffffff + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15) + text-transform: uppercase + display: flex + align-items: center + justify-content: center + margin: 0 !important + padding: 0 !important + + html.dark & + border-color: #1D0631 + +// ═════════════════════════════════════════════════════════════════════ +// HEADER MAIN COLUMN (#account_header — name, info, kudos) +// ═════════════════════════════════════════════════════════════════════ #account_header - margin-top: -15px - margin-left: 20px - width: 790px - position: relative - min-height: 145px - .info - margin-top: 3px + flex: 1 + margin: 0 !important + padding: 0 !important + width: auto !important + min-height: 0 + position: static + display: flex + flex-direction: column + gap: 8px + +// ─── Name Block ────────────────────────────────────────────────── +#account_name + margin: 0 + padding: 0 + width: 100% + float: none !important + h1 - @include site-link-color - width: 350px - .social-connect - padding-top: 5px - font-size: 22px -#kudo_section - position: inherit - form - margin-bottom: 0px - display: inline -@media only all and (min-width: 320px) and (max-width: 480px) - #account_header - width: auto - .social-connect - .btn-info - font-size: 10px !important - #account_name - margin-top: -134px - margin-left: 64px - h1 + font-size: 20px !important + font-weight: 600 !important + color: #111827 !important + line-height: 1.25 !important + margin: 0 0 4px !important + width: auto !important + border: none !important + letter-spacing: 0 + word-break: break-word + + @media (min-width: 640px) + font-size: 30px !important + + html.dark & + color: #ffffff !important + + a + color: inherit !important + text-decoration: none !important + &:hover + text-decoration: none !important + +// ─── Info row wrapper (location + website — stacks mobile, row sm+) ─ +#account_header .info_row + display: flex + flex-direction: column + gap: 2px + margin: 4px 0 8px !important + + @media (min-width: 640px) + flex-direction: row + flex-wrap: wrap + align-items: center + column-gap: 12px + row-gap: 2px + +// ─── Individual info item (location / website / affiliation) ───────── +#account_header .info + font-size: 12px + color: #6b7280 + line-height: 1.4 + margin: 0 !important + padding: 0 + display: flex + align-items: center + gap: 4px + + @media (min-width: 640px) + font-size: 14px + + html.dark & + color: #9ca3af + + i + flex-shrink: 0 + font-size: 12px !important + color: inherit + + @media (min-width: 640px) + font-size: 14px !important + + a + color: #5A2A82 + text-decoration: none + display: inline-flex + align-items: center + gap: 4px + transition: color 0.15s ease + min-width: 0 + + &:hover + text-decoration: underline + + html.dark & + color: #c084fc + +// ─── Action Buttons + Analyzed Timestamp (Figma: mt-4 flex-col gap-2) ─ +.header_actions + display: flex + flex-direction: column + gap: 8px + margin: 0 + padding: 0 + width: 100% + +// ─── Buttons row (flex-wrap row) ───────────────────────────────────── +.header_buttons + display: flex + gap: 8px + flex-wrap: wrap + align-items: center + + .action_button + padding: 6px 12px !important + border-radius: 8px !important + border: 1px solid #e5e7eb !important + background: #f3f4f6 !important + font-size: 12px !important + font-weight: 500 !important + color: #374151 !important + text-decoration: none !important + display: inline-flex + align-items: center + gap: 6px + transition: all 0.2s ease + height: auto !important + line-height: 1.2 !important + margin: 0 !important + + @media (min-width: 640px) font-size: 14px !important - width: auto + padding: 6px 14px !important + + &:hover + background: #e5e7eb !important + border-color: #d1d5db !important + text-decoration: none !important + + html.dark & + background: rgba(255, 255, 255, 0.1) !important + border-color: rgba(255, 255, 255, 0.2) !important + color: #ffffff !important + + &:hover + background: rgba(255, 255, 255, 0.2) !important + border-color: rgba(255, 255, 255, 0.3) !important + + i + font-size: 14px + flex-shrink: 0 + + .kudo_button_wrapper + display: inline-flex + margin: 0 !important + padding: 0 !important + + button, .btn, a + padding: 6px 16px !important + border-radius: 9999px !important + border: 1px solid rgba(90, 42, 130, 0.5) !important + background: #ffffff !important + font-size: 14px !important + font-weight: 500 !important + color: #5A2A82 !important + text-decoration: none !important + display: inline-flex + align-items: center + gap: 8px + transition: all 0.2s ease + height: auto !important + line-height: 1 !important + margin: 0 !important + cursor: pointer + + &:hover + border-color: #5A2A82 !important + background: #ffffff !important + text-decoration: none !important + + &.active, &.given + background: #5A2A82 !important + border-color: #5A2A82 !important + color: #ffffff !important + + &:hover + background: #4a1f6e !important + border-color: #4a1f6e !important + + html.dark & + background: #2D1548 !important + border-color: rgba(192, 132, 252, 0.5) !important + color: #c084fc !important + + &:hover + border-color: #c084fc !important + + &.active, &.given + background: #5A2A82 !important + border-color: #5A2A82 !important + color: #ffffff !important + +// ─── Kudo badges — horizontal scrolling row between header info and action buttons +// ─── Badges row in account header (high specificity to override badges.sass) ── +.account_header_inner > .mini-badges-section + display: flex !important + flex-direction: row !important + flex-wrap: nowrap !important + align-items: flex-start !important + overflow-x: auto !important + overflow-y: visible !important + gap: 0 !important + padding: 8px 0 4px !important + margin: 0 !important + position: relative !important + top: auto !important + right: auto !important + height: auto !important + float: none !important + -webkit-overflow-scrolling: touch + scrollbar-width: none + &::-webkit-scrollbar + display: none + + @media (min-width: 640px) + flex-wrap: wrap !important + overflow-x: visible !important + + @media (min-width: 1025px) + flex-shrink: 0 !important + align-self: flex-start !important + margin: 0 0 0 auto !important + width: auto !important + padding: 0 !important + flex-wrap: wrap !important + overflow-x: visible !important + + .account-badge + display: inline-block !important + float: none !important + flex-shrink: 0 + height: 48px !important + width: 48px !important + margin: 0 8px 31px 0 !important + transition: transform 0.15s ease + background-size: auto 48px + + &:hover + transform: scale(1.05) + + &.last + margin-right: 0 !important + + .pips + margin: 53px 5px 0 6px !important + width: 37px !important + +// ─── Kudo Button Override (strong specificity to override Bootstrap) ────────────────────────── +.kudo_button_wrapper form, +.kudo_button_wrapper button, +.kudo_button_wrapper .btn, +.kudo_action_button + all: revert !important + border-radius: 9999px !important + border: 1px solid rgba(90, 42, 130, 0.5) !important + background: #ffffff !important + font-size: 14px !important + font-weight: 500 !important + color: #5A2A82 !important + text-decoration: none !important + display: inline-flex !important + align-items: center !important + gap: 8px !important + transition: all 0.2s ease !important + height: auto !important + line-height: 1 !important + margin: 0 !important + cursor: pointer !important + + &:hover + border-color: #5A2A82 !important + background: #ffffff !important + text-decoration: none !important + + html.dark & + background: #2D1548 !important + border-color: rgba(192, 132, 252, 0.5) !important + color: #c084fc !important + + &:hover + border-color: #c084fc !important + background: #2D1548 !important + + i + font-size: 14px !important + +// ═════════════════════════════════════════════════════════════════════ +// SECTION HEADING (Figma SectionHeading) — h2/h3 with horizontal divider +// ═════════════════════════════════════════════════════════════════════ +#accounts_show_page #accounts_show > h2 + font-size: 16px !important + font-weight: 600 !important + color: #111827 !important + line-height: 1.3 !important + margin: 0 0 16px !important + padding: 0 !important + letter-spacing: 0 + float: none !important + width: 100% !important + display: flex !important + align-items: center + gap: 12px + white-space: nowrap + + html.dark & + color: #ffffff !important + + &::after + content: '' + flex: 1 + height: 1px + background: #e5e7eb + min-width: 40px + + html.dark & + background: rgba(90, 42, 130, 0.2) + +#accounts_show_page .mezzo.padding_one_top > h3 + font-size: 16px !important + font-weight: 600 !important + color: #111827 !important + line-height: 1.3 !important + margin: 0 0 16px !important + padding: 0 !important + letter-spacing: 0 + float: none !important + width: 100% !important + display: flex !important + align-items: center + gap: 12px + white-space: nowrap + + html.dark & + color: #ffffff !important + + &::after + content: '' + flex: 1 + height: 1px + background: #e5e7eb + min-width: 40px + + html.dark & + background: rgba(90, 42, 130, 0.25) + +// ═════════════════════════════════════════════════════════════════════ +// ACCOUNT SUMMARY — single white card with proper layout +// ═════════════════════════════════════════════════════════════════════ +#accounts_show_page #accounts_show + background: transparent + border: none + box-shadow: none + padding: 0 16px + margin: 0 auto 24px + max-width: 1024px + width: 100% + + @media (min-width: 640px) + padding: 0 24px + +// Summary Card wrapper (white bg, rounded, border, shadow) +.summary_card_wrapper + background: #ffffff + border: 1px solid #e5e7eb + border-radius: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) + overflow: hidden + width: 100% + + html.dark & + background: #2D1548 + border-color: rgba(90, 42, 130, 0.2) + +// Grid layout: stacks on mobile (1 column), 2 columns on md+ +.summary_grid + display: grid + grid-template-columns: 1fr + gap: 0 + width: 100% + + @media (min-width: 768px) + grid-template-columns: 1fr 1fr + +// Left column: Description + Projects Used +.summary_column_left + padding: 16px !important + border-bottom: 1px solid #f3f4f6 + margin: 0 !important + + @media (min-width: 640px) + padding: 24px !important + + @media (min-width: 768px) + border-bottom: none + border-right: 1px solid #f3f4f6 + padding: 24px !important + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.2) + + @media (min-width: 768px) + border-right-color: rgba(90, 42, 130, 0.2) + +// Right column: Baseball Card stats +.summary_column_right + padding: 16px !important + margin: 0 !important + + @media (min-width: 640px) + padding: 24px !important + +// Account analysis timestamp — now in header below action buttons +.header_actions #account_analysis_timestamp, +.header_actions #analysis_timestamp + float: none !important + color: #4b5563 !important + font-size: 12px + font-style: normal + margin: 0 !important + padding: 0 !important + display: flex + align-items: center + gap: 6px + + html.dark & + color: #9ca3af !important + + i + font-style: normal + color: inherit + font-size: 12px + + abbr + border-bottom: none + text-decoration: none + cursor: default + + abbr + border-bottom: none + text-decoration: none + cursor: default + +// Description body +#accounts_show_page #accounts_show .margin_bottom_10px + font-size: 14px + color: #4b5563 + line-height: 1.6 + margin-bottom: 16px !important + + html.dark & + color: #d1d5db + +// "Projects Used" subheading — purple uppercase tracked (Figma: mb-3 = 12px, no top margin) +#accounts_show_page #accounts_show .projects_used + font-size: 12px !important + font-weight: 600 !important + color: #5A2A82 !important + text-transform: uppercase + letter-spacing: 0.1em + margin: 0 0 12px !important + + html.dark & + color: #c084fc !important + + a + color: inherit !important + text-decoration: none + +// Stacked project icons +#accounts_show_page #accounts_show .stacked_projects + display: flex !important + flex-wrap: wrap + gap: 6px + margin-bottom: 16px + + a + border: 0 !important + display: inline-flex + + img + width: 36px !important + height: 36px !important + border-radius: 8px + border: 1px solid #e5e7eb + padding: 0 !important + margin: 0 !important + transition: transform 0.15s ease + object-fit: cover + + &:hover + transform: scale(1.05) + + html.dark & + border-color: rgba(90, 42, 130, 0.3) + + p + margin: 0 !important + display: inline-flex + color: #5A2A82 + font-size: 12px + align-items: center + padding-left: 4px + + a + color: inherit + text-decoration: none + +// ═════════════════════════════════════════════════════════════════════ +// BASEBALL CARD — clean stats list (label / value rows) +// ═════════════════════════════════════════════════════════════════════ +#accounts_show_page .baseball_card + width: 100% !important + float: none !important + display: flex + flex-direction: column + gap: 10px + + .col-md-3, .col-md-4, .col-md-7, + .col-xs-7, .col-sm-7, .col-xs-3, .col-sm-3, .col-xs-4, .col-sm-4 + padding: 0 !important + float: none !important + width: auto !important + + .statistic + width: 100% !important + min-height: auto + margin: 0 !important + padding: 0 !important + border-bottom: none !important + + .inner + width: 100% !important + line-height: 1.4 + display: flex + align-items: flex-start + justify-content: space-between + gap: 12px + + // Figma: flex-shrink-0 w-[42%] sm:w-44 text-xs sm:text-sm text-gray-500 + .name + width: 42% !important + font-size: 12px + font-weight: 400 + color: #6b7280 + text-align: left !important + margin: 0 !important + flex-shrink: 0 + line-height: 1.4 + + @media (min-width: 640px) + width: 176px !important + font-size: 14px + + html.dark & + color: #9ca3af + + // Figma: text-gray-700 text-right text-xs sm:text-sm + .value + font-size: 12px + font-weight: 500 + color: #374151 + margin: 0 !important + text-align: right + flex: 1 + line-height: 1.4 + min-width: 0 + + @media (min-width: 640px) + font-size: 14px + + html.dark & + color: #e5e7eb + + a + color: #5A2A82 + text-decoration: none + font-weight: 600 + + &:hover + text-decoration: underline + + html.dark & + color: #c084fc + + .language-box + font-size: 12px !important + font-weight: 700 !important + margin: 0 !important + padding: 2px 8px !important + border-radius: 4px !important + display: inline-block !important + min-width: auto !important + line-height: 1.4 !important + background-color: #f97316 !important + color: #ffffff !important + text-transform: none + + .right_sm + font-size: 11px + margin-left: 0 + margin-top: 2px + line-height: 1.4 + color: #4b5563 + display: block + width: 100% + font-weight: 400 + + html.dark & + color: #9ca3af + + // ─── Figma: commits count and org count are bold ────────────────── + .statistic--commits .value + font-weight: 700 !important + color: #111827 !important + + html.dark & + color: #ffffff !important + + .statistic--orgs .value + font-weight: 700 !important + color: #111827 !important + + html.dark & + color: #ffffff !important + + .right_sm + font-weight: 400 !important + color: #4b5563 !important + + html.dark & + color: #9ca3af !important + +// ═════════════════════════════════════════════════════════════════════ +// DEV HISTORY — section heading + chart cards +// ═════════════════════════════════════════════════════════════════════ +#accounts_show_page .mezzo.padding_one_top + border-top: none !important + margin: 24px auto 0 !important + padding: 0 16px !important + max-width: 1024px + + @media (min-width: 640px) + padding: 0 24px !important + + // ─── .dev_chart_section IS the card — wraps title + chart + JS-injected legend + .dev_chart_section + background: #ffffff + border: 1px solid #e5e7eb + border-radius: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) + padding: 16px + margin-bottom: 24px + overflow: hidden + + @media (min-width: 640px) + padding: 20px + + html.dark & + background: #2D1548 + border-color: rgba(90, 42, 130, 0.2) + + &:last-child + margin-bottom: 0 + + // ─── Section headings (card header with horizontal divider) ───────────── + .dev_chart_section #commit_projects, + .dev_chart_section #commits_lang + float: none !important + clear: both + display: flex !important + align-items: center + gap: 12px + margin: 0 0 16px !important + padding: 0 0 12px !important + border-bottom: 1px solid #f3f4f6 !important + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.15) !important + + h4 + font-size: 15px !important + font-weight: 600 !important + color: #111827 !important + margin: 0 !important + padding: 0 + line-height: 1.3 !important + white-space: nowrap + border: none !important + + html.dark & + color: #ffffff !important + + a + color: inherit !important + text-decoration: none !important + + &:hover + text-decoration: underline !important + + // ─── Date range label ──────────────────────────────────────────────────── + .dev_chart_section .soft.margin_top_10.margin_left_20, + .dev_chart_section .soft.margin_left_20 + float: none !important + clear: both + color: #4b5563 !important + font-style: normal + font-size: 12px !important + margin: -4px 0 12px !important + display: block + + html.dark & + color: #9ca3af !important + + // ─── .margin_top_10 wrapper inside commits_by_language — reset margin ──── + .dev_chart_section > .margin_top_10 + margin-top: 0 !important + + // ─── Override fixed widths from streamgraph.sass ───────────────────────── + // #ohloh_streamgraph has width:715px float:left margin-left:15px + // #ohloh_stream (child SVG) has width:940px !important + // #streamgraph_legend is injected AFTER #ohloh_streamgraph by JS as a sibling + // Both end up inside .dev_chart_section, so card contains everything + .dev_chart_section #ohloh_streamgraph + width: 100% !important + float: none !important + margin-left: 0 !important + margin-right: 0 !important + overflow: visible + + svg, #ohloh_stream + width: 100% !important + max-width: 100% !important + + .dev_chart_section #streamgraph_legend + float: none !important + width: 100% !important + white-space: normal !important + margin-right: 0 !important + margin-top: 12px + border: none !important + overflow: visible + + // ─── Constrain all chart canvases to card width ────────────────────────── + .dev_chart_section .chart, + .dev_chart_section #project_contributions + max-width: 100% !important + width: 100% !important + margin-left: 0 !important + +// ═════════════════════════════════════════════════════════════════════ +// ADMIN PANEL — page wrapper aligns with summary card (same padding) +// ═════════════════════════════════════════════════════════════════════ +.admin_panel_page_wrapper + max-width: 1024px + margin: 0 auto 24px + padding: 0 16px + + @media (min-width: 640px) + padding: 0 24px + + // ── Development History section ─────────────────────────────────────── +.dev-history-section + padding: 0 + margin: 0 + + > h2 + font-size: 16px !important + font-weight: 600 !important + color: #111827 !important + line-height: 1.3 !important + margin: 0 0 16px !important + padding: 0 !important + letter-spacing: 0 + float: none !important + width: 100% !important + display: flex !important + align-items: center + gap: 12px + white-space: nowrap + + html.dark & + color: #ffffff !important + + &::after + content: '' + flex: 1 + height: 1px + background: #e5e7eb + min-width: 40px + + html.dark & + background: rgba(90, 42, 130, 0.2) + +// ── Admin card container (inside wrapper — full width within padding) ─ +#admin_actions_opened, #admin_actions_closed + border-radius: 16px !important + border: 1px solid rgba(251, 191, 36, 0.4) !important + background: #ffffff !important + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important + padding: 0 !important + margin: 0 !important + overflow: hidden + width: 100% + + html.dark & + background: #1a0e2e !important + border-color: rgba(251, 191, 36, 0.3) !important + +// ─── Shared amber header bar (used in both open/closed states) ──── +.admin_panel_header + display: flex !important + align-items: center !important + justify-content: space-between !important + width: 100% !important + padding: 12px 16px !important + background: #fffbeb !important + border-bottom: 1px solid rgba(251, 191, 36, 0.3) !important + text-decoration: none !important + cursor: pointer + box-sizing: border-box + + html.dark & + background: rgba(120, 53, 15, 0.25) !important + border-bottom-color: rgba(251, 191, 36, 0.2) !important + + @media (min-width: 640px) + padding: 14px 20px !important + +// When collapsed, no border-bottom (nothing below the header) +#admin_actions_closed .admin_panel_header + border-bottom: none !important + +.admin_header_left + display: flex + align-items: center + gap: 10px + +.admin_lock_badge + width: 28px + height: 28px + border-radius: 8px + background: rgba(251, 191, 36, 0.2) + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + + html.dark & + background: rgba(251, 191, 36, 0.3) + + i + font-size: 13px !important + color: #b45309 !important + margin: 0 !important + line-height: 1 + + html.dark & + color: #fbbf24 !important + +.admin_panel_title + font-size: 12px !important + font-weight: 700 !important + color: #92400e !important + text-transform: uppercase + letter-spacing: 0.08em + line-height: 1 + + html.dark & + color: #fcd34d !important + +.admin_chevron_btn + display: flex !important + align-items: center + color: #b45309 !important + text-decoration: none !important + background: transparent !important + border: none !important + box-shadow: none !important + padding: 2px 4px !important + margin: 0 !important + height: auto !important + line-height: 1 !important + cursor: pointer !important + + html.dark & + color: #fbbf24 !important + + i + font-size: 14px !important + margin: 0 !important + +// ─── Panel body (expanded content) ─────────────────────────────── +.admin_panel_body + background: #ffffff + display: flex + flex-direction: column + + html.dark & + background: #1a0e2e + +// ─── Info section (Figma: px-4 sm:px-5 py-4 space-y-4) ───────────── +.admin_info_section + padding: 16px !important + border-bottom: 1px solid #f3f4f6 + display: flex + flex-direction: column + gap: 16px + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.15) + + @media (min-width: 640px) + padding: 16px 20px !important + + p + margin: 0 !important + padding: 0 + color: #4b5563 + font-size: 13px + line-height: 1.5 + + html.dark & + color: #d1d5db + +// ─── Shared label with icon (Email Address / Open Hub Activity / Admin Actions) +.admin_info_label + display: flex + align-items: center + gap: 6px + margin: 0 0 8px !important + + i + font-size: 12px !important + color: #9ca3af !important + flex-shrink: 0 + + html.dark & + color: #6b7280 !important + + span + font-size: 11px !important + font-weight: 700 !important + color: #9ca3af !important + text-transform: uppercase + letter-spacing: 0.1em + line-height: 1.4 + + html.dark & + color: #6b7280 !important + +.admin_danger_label + i, span + color: #f87171 !important + + html.dark & + color: rgba(248, 113, 113, 0.8) !important + +// ─── Email link ────────────────────────────────────────────────────── +.admin_email_link + margin: 0 !important + + a + font-size: 13px !important + color: #5A2A82 !important + text-decoration: none + display: inline-flex + align-items: center + gap: 4px + word-break: break-all + + &:hover + text-decoration: underline + + html.dark & + color: #c084fc !important + +// ─── Activity mini-cards (Figma: grid grid-cols-1 sm:grid-cols-3) ──── +.admin_activity_cards + display: grid + grid-template-columns: 1fr + gap: 8px + + @media (min-width: 640px) + grid-template-columns: repeat(3, 1fr) + +.admin_activity_card + display: flex + align-items: center + gap: 10px + border: 1px solid #e5e7eb + border-radius: 12px + padding: 10px 12px + + html.dark & + background: #2D1548 + border-color: rgba(90, 42, 130, 0.2) + + > i + font-size: 16px !important + color: #5A2A82 !important + flex-shrink: 0 + + html.dark & + color: #c084fc !important + +.admin_activity_card_body + min-width: 0 + flex: 1 + + p.admin_activity_card_title + font-size: 11px !important + color: #9ca3af !important + margin: 0 0 2px !important + padding: 0 + + html.dark & + color: #6b7280 !important + + a + font-size: 13px !important + font-weight: 600 !important + color: #5A2A82 !important + text-decoration: none + display: block + + &:hover + text-decoration: underline + + html.dark & + color: #c084fc !important + +// ─── Actions section (Figma: px-4 sm:px-5 py-4 bg-red-50/40) ──────── +.admin_actions_section + padding: 16px !important + background: rgba(254, 242, 242, 0.4) + + html.dark & + background: rgba(127, 29, 29, 0.08) + + @media (min-width: 640px) + padding: 16px 20px !important + +// ─── Action buttons grid (Figma: grid-cols-2 sm:flex sm:flex-wrap) ── +.admin_btns_grid + display: grid + grid-template-columns: repeat(2, 1fr) + gap: 8px + + @media (min-width: 640px) + display: flex + flex-wrap: wrap + + .admin_disabled_btn + display: contents + +.admin_actions_section p + margin: 0 !important + padding: 0 + +// ─── Color-coded action buttons (inside grid/flex) ────────────────── +.admin_btns_grid .btn, +.admin_btns_grid a.btn, +.admin_btns_grid button.btn + border-radius: 10px !important + padding: 8px 12px !important + font-size: 12px !important + font-weight: 600 !important + margin: 0 !important + border: 1px solid !important + line-height: 1 !important + height: auto !important + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important + transition: all 0.15s ease + color: #ffffff !important + display: flex !important + align-items: center + justify-content: center + gap: 6px + text-align: center + + i + font-size: 12px + + &:hover + transform: scale(0.98) + text-decoration: none !important + color: #ffffff !important + + &.btn-primary + background-color: #5A2A82 !important + border-color: #5A2A82 !important + &:hover + background-color: #4a1f6e !important + + &.btn-warning + background-color: #f97316 !important + border-color: #ea580c !important + &:hover + background-color: #ea580c !important + + &.btn-success + background-color: #059669 !important + border-color: #047857 !important + &:hover + background-color: #047857 !important + + &.btn-danger + background-color: #dc2626 !important + border-color: #b91c1c !important + &:hover + background-color: #b91c1c !important + + &.btn-info + background-color: #1e3a8a !important + border-color: #1e40af !important + &:hover + background-color: #1e40af !important + + &.btn-default[disabled], &[disabled] + background-color: #e5e7eb !important + border-color: #d1d5db !important + color: #9ca3af !important + cursor: not-allowed + +// ─── Close admin panel text button (Figma: col-span-2 ghost button) ── +.admin_close_text_btn + grid-column: 1 / -1 + display: flex !important + align-items: center + justify-content: center + gap: 6px + padding: 8px 12px !important + border-radius: 10px !important + border: 1px solid transparent !important + background: transparent !important + font-size: 12px !important + font-weight: 500 !important + color: #6b7280 !important + text-decoration: none !important + cursor: pointer + transition: all 0.15s ease + box-shadow: none !important + height: auto !important + margin: 0 !important + line-height: 1 !important + + i + font-size: 11px !important + + &:hover + color: #374151 !important + border-color: #e5e7eb !important + background: transparent !important + text-decoration: none !important + + html.dark & + color: #9ca3af !important + + &:hover + color: #e5e7eb !important + border-color: rgba(90, 42, 130, 0.3) !important + +.dev_history + padding-top: 1em + +// ═════════════════════════════════════════════════════════════════════ +// RESPONSIVE OVERRIDES — Mobile & Tablet +// ═════════════════════════════════════════════════════════════════════ +@media only all and (min-width: 320px) and (max-width: 480px) + .account_header_inner + padding: 16px 12px 14px !important + gap: 12px + + .header_flex_container + gap: 12px + #account_icon - img - width: 90px - height: 90px + width: 64px !important + height: 64px !important + + #account_name h1 + font-size: 18px !important + + .header_buttons + gap: 6px + + .action_button + padding: 5px 10px !important + font-size: 11px !important + + #kudo_section + gap: 8px + + .social-connect + gap: 6px + + .btn-info, .btn-mini, .btn + padding: 5px 12px !important + font-size: 12px !important + #accounts_show_page - .primary_language_bg - width: 90px - #accounts_show - h2 - font-size: 15px !important - .projects_used - font-size: 14px !important - #admin_actions_opened + #accounts_show > h2 + font-size: 14px !important + + .baseball_card + .name + font-size: 11px + + .value + font-size: 12px + + .admin_actions_section + padding: 12px !important + .btn - font-size: 10px - .mezzo - h3 - font-size: 15px - #commit_projects - h4 - font-size: 13px - #commits_lang - h4 - font-size: 13px - #project_contributions - svg.highcharts-root - font-size: 10px !important - .highcharts-axis-labels - text + padding: 6px 10px !important + font-size: 11px !important + + .admin_btns_grid + grid-template-columns: 1fr !important + + .admin_close_text_btn + grid-column: 1 !important + + #accounts_show_page .mezzo.padding_one_top + > h3 + font-size: 14px !important + + > .col-sm-12, > .col-md-12 + padding: 14px !important + + #commit_projects h4, #commits_lang h4 + font-size: 13px !important + + #project_contributions + svg.highcharts-root + font-size: 10px !important + .highcharts-axis-labels text font-size: 10px !important - .highcharts-legend-item - text + .highcharts-legend-item text font-size: 6px !important + @media (min-width: 540px) and (max-width: 1024px) - #account_header - width: 83.33333333%; - margin-left: 8px - #account_name - h1 - font-size: 24px !important - width: auto - margin-top: 30px + .account_header_inner + padding: 20px 20px 18px !important + + #account_name h1 + font-size: 24px !important + +@media (min-width: 1025px) + .account_header_inner + flex-direction: row !important + flex-wrap: wrap !important + align-items: flex-start !important + + .account_header_inner > .header_flex_container + flex: 1 1 0% !important + min-width: 0 !important + + .account_header_inner > .header_actions + flex: 0 0 100% !important diff --git a/app/assets/stylesheets/ace-chosen-overrides.sass b/app/assets/stylesheets/ace-chosen-overrides.sass index 41b3bcda9..d018fbdc0 100644 --- a/app/assets/stylesheets/ace-chosen-overrides.sass +++ b/app/assets/stylesheets/ace-chosen-overrides.sass @@ -40,7 +40,7 @@ &.chosen-disabled .chosen-single abbr:hover:after color: #464646 -.chosen-single div b +.chosen-container-single .chosen-single div b background: none !important &:before content: "\f0d7" @@ -158,7 +158,7 @@ .chosen-container-active.chosen-with-drop .chosen-single @include chzn-container-border-color -@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-resolution: 144dpi) +@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi), only screen and (min-resolution: 1.5dppx) .chosen-rtl .chosen-search input background-image: none !important background-repeat: no-repeat !important diff --git a/app/assets/stylesheets/alias.sass b/app/assets/stylesheets/alias.sass index c57bd86cb..518c0aa9a 100644 --- a/app/assets/stylesheets/alias.sass +++ b/app/assets/stylesheets/alias.sass @@ -1,16 +1,188 @@ +// Design System Variables +$purple-primary: #5A2A82 +$gray-500: #6b7280 +$gray-200: #e5e7eb +$yellow-accent: #FFB91A + +// System Font Stack +$font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif + .project_content_title - :height 50px + height: 72px + font-family: $font-family + .alias + font-family: $font-family + .undone - :text-decoration line-through - :color #808080 + text-decoration: line-through + color: $gray-500 + .pending - :color #8B0000 - :padding-left 18px - :background asset_url('icons/fact_warning.png') left center no-repeat + color: $purple-primary + padding-left: 18px + background: asset_url('icons/fact_warning.png') left center no-repeat + font-weight: 500 + li - :margin-left 25px - :margin-bottom 8px - :border-bottom 1px solid #ddd - :padding-bottom 5px + margin-left: 25px + margin-bottom: 12px + border-bottom: 1px solid $gray-200 + padding-bottom: 8px + font-weight: 400 + line-height: 1.6 + +// Dark Mode +html.dark, +body.dark + .alias + .undone + color: #94a3b8 + + .pending + color: $yellow-accent + + li + border-bottom-color: rgba(255, 255, 255, 0.1) + +// ────────────────────────────────────────────────────────────────────────────── +// Alias New Page (#alias-new-page) +// ────────────────────────────────────────────────────────────────────────────── + +#alias-new-page + padding: 24px 0 + + .alias-page-header + margin-bottom: 24px + + .alias-page-title + font-size: 22px + font-weight: 700 + color: #111827 + margin: 0 0 8px 0 + display: flex + align-items: center + gap: 8px + flex-wrap: wrap + + html.dark & + color: #ffffff + + a + color: #5A2A82 + text-decoration: none + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + &__sep + color: #9ca3af + font-weight: 400 + + // ── Form fields ── + + .alias-fields + padding: 4px 0 + + .alias-field-group + margin-bottom: 20px + + .alias-label + display: block + font-size: 13px + font-weight: 600 + color: #374151 + margin-bottom: 6px + + html.dark & + color: #d1d5db + + .alias-select-wrap + width: 100% + + select + box-sizing: border-box + width: 100% + border: 2px solid #e5e7eb !important + border-radius: 16px !important + padding: 10px 16px !important + font-size: 14px + height: auto !important + background: #ffffff !important + color: #111827 !important + transition: border-color 0.2s, box-shadow 0.2s + appearance: auto + + &:focus + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 3px rgba(90, 42, 130, 0.1) !important + + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + + &:focus + border-color: #ffb91a !important + box-shadow: 0 0 0 3px rgba(255, 185, 26, 0.1) !important + + // chosen.js container + .chosen-container + width: 100% !important + + .chosen-single, + .chosen-choices + border: 2px solid #e5e7eb !important + border-radius: 16px !important + padding: 8px 16px !important + background: #ffffff !important + box-shadow: none !important + font-size: 14px !important + color: #111827 !important + + html.dark & + background: #1D0631 !important + border-color: #5A2A82 !important + color: #ffffff !important + + .chosen-container-active .chosen-single, + .chosen-container-active .chosen-choices + border-color: #5A2A82 !important + box-shadow: 0 0 0 3px rgba(90, 42, 130, 0.1) !important + + html.dark & + border-color: #ffb91a !important + box-shadow: 0 0 0 3px rgba(255, 185, 26, 0.1) !important + + .alias-actions + padding-top: 8px + display: flex + gap: 10px + + .alias-submit + height: 40px + padding: 0 24px + border-radius: 20px + font-size: 14px + font-weight: 600 + + // ── Responsive ── + + @media (max-width: 767px) + padding: 16px 0 + + .alias-page-title + font-size: 17px + + .alias-actions + flex-direction: column + + .alias-submit + width: 100% + @media (min-width: 768px) and (max-width: 1024px) + padding: 20px 0 diff --git a/app/assets/stylesheets/analyses.sass b/app/assets/stylesheets/analyses.sass index 902c4d18f..55372f3c4 100644 --- a/app/assets/stylesheets/analyses.sass +++ b/app/assets/stylesheets/analyses.sass @@ -12,3 +12,5 @@ #analyses_language_table .center text-align: center + .bar + min-width: 60px diff --git a/app/assets/stylesheets/api/custom.sass b/app/assets/stylesheets/api/custom.sass index e96b14288..11cce9759 100644 --- a/app/assets/stylesheets/api/custom.sass +++ b/app/assets/stylesheets/api/custom.sass @@ -1687,59 +1687,43 @@ padding: 6px #cve-cvss3, #cvss3, #cve-cvss3-mobile, #cvss3-mobile - background: asset-url("charts/watermark_340.png") no-repeat center - background-size: contain position: relative width: 100% height: 100% - html.dark & - background-image: none - - &::before - content: '' - position: absolute - top: 0 - left: 0 - right: 0 - bottom: 0 - background: asset-url("charts/watermark_340.png") no-repeat center - background-size: contain - filter: hue-rotate(260deg) saturate(1.2) brightness(0.3) - opacity: 0.2 - pointer-events: none - - .highcharts-axis-labels text, - .highcharts-axis-title text - fill: #d1d5db !important - stroke: none !important - stroke-width: 0 !important - -webkit-text-stroke: 0 !important - paint-order: stroke fill !important - - .highcharts-legend text - fill: #d1d5db !important - stroke: none !important - stroke-width: 0 !important - -webkit-text-stroke: 0 !important - paint-order: stroke fill !important - - svg text - stroke: none !important - stroke-width: 0 !important - -webkit-text-stroke: 0 !important - - text[stroke] - stroke: none !important - stroke-width: 0 !important - -webkit-text-stroke: 0 !important - - .highcharts-axis-line - stroke: #6b7280 !important - - .highcharts-grid-line - stroke: #374151 !important - opacity: 0.3 +html.dark + #cve-cvss3, #cvss3, #cve-cvss3-mobile, #cvss3-mobile + .highcharts-axis-labels text, + .highcharts-axis-title text + fill: #d1d5db !important + stroke: none !important + stroke-width: 0 !important + -webkit-text-stroke: 0 !important + paint-order: stroke fill !important + + .highcharts-legend text + fill: #d1d5db !important + stroke: none !important + stroke-width: 0 !important + -webkit-text-stroke: 0 !important + paint-order: stroke fill !important + + svg text + stroke: none !important + stroke-width: 0 !important + -webkit-text-stroke: 0 !important + + text[stroke] + stroke: none !important + stroke-width: 0 !important + -webkit-text-stroke: 0 !important + + .highcharts-axis-line + stroke: #6b7280 !important + + .highcharts-grid-line + stroke: #374151 !important + opacity: 0.3 .search-container display: flex @@ -1810,4 +1794,4 @@ color: #d1d5db .search-result-hint - color: #9ca3af + color: #9ca3af \ No newline at end of file diff --git a/app/assets/stylesheets/api_keys.sass b/app/assets/stylesheets/api_keys.sass index a57b7846e..7395c36e2 100644 --- a/app/assets/stylesheets/api_keys.sass +++ b/app/assets/stylesheets/api_keys.sass @@ -1,5 +1,68 @@ +.limit-label + color: #111827 + html.dark & + color: #e5e7eb + +#api-key-form + .help-block + color: #595959 !important + html.dark & + color: #9ca3af !important + + html.dark & + legend + color: #e5e7eb !important + border-bottom-color: #4b5563 !important + + .form-control + width: 85% !important + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + -webkit-border-radius: 16px !important + -moz-border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + &::placeholder + color: #9ca3af !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #d1d5db !important + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + background: #ffffff !important + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &::placeholder + color: #6b7280 !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.1) !important + border-color: #6b7280 !important + &:focus, &:focus-visible + border-color: #ffb91a !important + background: #1D0631 !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + outline: none !important + label#request_limit font-size: 13px + display: flex + align-items: flex-start + gap: 8px + font-weight: normal + input[type="checkbox"] + margin-top: 3px + flex-shrink: 0 .api_keys .padding_20 @@ -9,6 +72,8 @@ label#request_limit color: #666 font-size: 12px font-weight: normal + html.dark & + color: #9ca3af #api_key_request_info .inline diff --git a/app/assets/stylesheets/autocomplete.sass b/app/assets/stylesheets/autocomplete.sass index 9cb5f06e2..317784611 100644 --- a/app/assets/stylesheets/autocomplete.sass +++ b/app/assets/stylesheets/autocomplete.sass @@ -1,58 +1,70 @@ .ui-autocomplete - :padding 0px - :border 1px solid #0082C6 - :background-color white - :overflow-y auto - :overflow-x hidden - :max-height 16.0em - :z-index 99999 - - :width 15.0em - :list-style-position outside - :list-style none - :padding 0 - :margin 0 + width: 160px + padding: 0 + margin: 0 + background-color: #ffffff + border: 1px solid #d1d5db + border-radius: 8px + overflow: hidden + overflow-y: auto + overflow-x: hidden + max-height: 16.0em + z-index: 99999 + list-style-position: outside + list-style: none + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23) + transition: all 0.2s ease + + html.dark & + background-color: #1D0631 + border-color: #5A2A82 + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.38), 0 6px 6px rgba(0, 0, 0, 0.46) li - :margin 0px - :padding 2px 5px - :cursor default - :display block - // COMMENT - if width will be 100% horizontal scrollbar will apear - when scroll mode will be used - width 100% - :font menu - :font-size 12px - // COMMENT - it is very important, if line-height not setted or setted - in relative units scroll will be broken in firefox - :line-height 16px - :overflow hidden + margin: 0 + padding: 8px 16px + cursor: default + display: block + font-size: 14px + font-weight: 400 + line-height: 20px + overflow: hidden + background: transparent + transition: background 0.15s ease, color 0.15s ease + + &:hover + background-color: #f3f4f6 + cursor: pointer + + html.dark & + background: #374151 a - :color black - :text-decoration none - :border 0 - :display block + color: #111827 + text-decoration: none + border: 0 + display: block - li:hover - :background-color $c2_light - :cursor pointer + html.dark & + color: #ffffff .ui-state-hover - :background-color $c2_light + background-color: #5A2A82 !important + color: #ffffff + + html.dark & + background-color: #ffb91a !important + color: #5A2A82 .ui-autocomplete-loading - :background Window asset_url('icons/auto_complete_spinner.gif') right center no-repeat + background: window asset_url('icons/auto_complete_spinner.gif') right center no-repeat .ui-autocomplete-moreItems - :text-align center - :margin 0px - :padding 0px 5px - :cursor default - :display block - :width 100% - :overflow hidden - :-moz-user-select none - :-khtml-user-select none + text-align: center + margin: 0 + padding: 8px 16px + cursor: default + display: block + width: 100% + overflow: hidden + user-select: none diff --git a/app/assets/stylesheets/badges.sass b/app/assets/stylesheets/badges.sass index 0eeafc679..fa3bd0de8 100644 --- a/app/assets/stylesheets/badges.sass +++ b/app/assets/stylesheets/badges.sass @@ -1,3 +1,20 @@ +.describer-badge + background: asset_url('badges/48-pixel-badge-sprite.png') no-repeat -48px 0 +.repo-person-badge + background: asset_url('badges/48-pixel-badge-sprite.png') no-repeat -288px 0 +.project-manager-badge + background: asset_url('badges/48-pixel-badge-sprite.png') no-repeat -240px 0 +.stacker-badge + background: asset_url('badges/48-pixel-badge-sprite.png') no-repeat -336px 0 +.org-manager-badge + background: asset_url('badges/48-pixel-badge-sprite.png') no-repeat -192px 0 +.fos-ser-badge + background: asset_url('badges/48-pixel-badge-sprite.png') no-repeat -96px 0 +.taxonomist-badge + background: asset_url('badges/48-pixel-badge-sprite.png') no-repeat 0 0 +.kudo-rank-badge + background: asset_url('badges/48-pixel-badge-sprite.png') no-repeat -144px 0 + .mini-badges-section position: absolute height: 62px @@ -22,38 +39,39 @@ .kudo-rank-badge background: asset_url('badges/48-pixel-badge-sprite.png') no-repeat -144px 0 - .pip-0000 - background: asset_url('badges/pips-7-high.png') no-repeat 0 0 - .pip-0001 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -7px - .pip-0010 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -14px - .pip-0011 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -21px - .pip-0100 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -28px - .pip-0101 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -35px - .pip-0110 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -42px - .pip-0111 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -49px - .pip-1000 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -56px - .pip-1001 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -63px - .pip-1010 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -70px - .pip-1011 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -77px - .pip-1100 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -84px - .pip-1101 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -91px - .pip-1110 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -98px - .pip-1111 - background: asset_url('badges/pips-7-high.png') no-repeat 0 -105px + +.pip-0000 + background: asset_url('badges/pips-7-high.png') no-repeat 0 0 +.pip-0001 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -7px +.pip-0010 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -14px +.pip-0011 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -21px +.pip-0100 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -28px +.pip-0101 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -35px +.pip-0110 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -42px +.pip-0111 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -49px +.pip-1000 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -56px +.pip-1001 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -63px +.pip-1010 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -70px +.pip-1011 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -77px +.pip-1100 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -84px +.pip-1101 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -91px +.pip-1110 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -98px +.pip-1111 + background: asset_url('badges/pips-7-high.png') no-repeat 0 -105px .pips width: 37px @@ -90,9 +108,9 @@ #account_header .mini-badges-section position: relative - margin: 27px 4px 0 0 + margin: 0 .account-badge - margin: 0 20px 31px 0 + margin: 0 8px 31px 0 height: 48px width: 48px &.last @@ -100,9 +118,21 @@ @media only all and (min-width: 320px) and (max-width: 480px) #account_header .mini-badges-section - margin: 5px 43px 0 0 + margin: 0 + .account-badge + margin: 0 4px 20px 0 + height: 32px + width: 32px + background-size: auto 32px !important +@media only all and (min-width: 481px) and (max-width: 767px) + #account_header + .mini-badges-section + margin: 0 .account-badge - margin: 0 20px 20px 0 + margin: 0 6px 26px 0 + height: 40px + width: 40px + background-size: auto 40px !important #outside_committers_list .pips, #orgs_affiliated_list .pips margin: 0px 0px 0px 22px !important diff --git a/app/assets/stylesheets/base.sass b/app/assets/stylesheets/base.sass index 2e50269cc..664c6be75 100644 --- a/app/assets/stylesheets/base.sass +++ b/app/assets/stylesheets/base.sass @@ -1,14 +1,6 @@ @import oh-styles // base mixins -=sans_font - font-family: 'Roboto' -=serif_font - font-family: 'Roboto' -=arial_font - font-family: 'Roboto' -=lucida_font - font-family: 'Roboto' =small_font font-size: .91667em =tiny_font @@ -16,15 +8,9 @@ =link_blue @include site-link-color -// font awesome custom styles -#commits_index_page, -#commits_summary_page, -#mini_account_row - [class^="icon-"], [class*=" icon-"] - font-family: Roboto !important - body text-rendering: optimizeLegibility + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important a @include site-link-color @@ -162,7 +148,21 @@ ul.unstyled, ol.unstyled font-size: 12px thead th - border-bottom: 1px solid #ddd !important + color: #000 !important + background-color: #5b5d6237 !important + border-bottom: 1px solid #888 !important + + html.dark & + background-color: #3A1D58 !important + border-bottom-color: #5A2A82 !important + +.table.table-striped.table-condensed + tbody + tr:nth-child(odd) td, + tr:nth-child(even) td + html.dark & + background-color: #2D1548 !important + color: #ffffff !important .pre-line white-space: pre-line @@ -174,5 +174,61 @@ ul.unstyled, ol.unstyled .pagination @include site-pagination-colors +.modern-pagination + @include modern-pagination-styles + .font-1em font-size: 1em + +// Override any third-party CSS that sets display: inline on icon elements +a [class^="icon-"], +a [class*=" icon-"] + &:not(.icon-letter) + display: inline-flex !important + +// Icon Container - Global Baseline Styling +.icon-container + display: inline-flex !important + align-items: center + justify-content: center + width: 48px + height: 48px + border-radius: 12px + overflow: hidden + background-color: #f9fafb + border: 1px solid rgba(229, 231, 235, 0.8) + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) + flex-shrink: 0 + + html.dark & + background-color: #1D0631 + border-color: rgba(255, 255, 255, 0.1) + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.24) + + &.has-logo + background-color: transparent + border: none + box-shadow: none + + img + width: 100% + height: 100% + object-fit: contain + display: block + + @media (min-width: 640px) + width: 56px + height: 56px + .icon-letter + font-size: 26px + +// Icon Letter Fallback - Global Styling +.icon-letter + font-size: 22px + font-weight: 700 + color: #5A2A82 + text-transform: uppercase + user-select: none + + html.dark & + color: #ffb91a diff --git a/app/assets/stylesheets/bootstrap.sass b/app/assets/stylesheets/bootstrap.sass index 9804cfce4..e37c97869 100644 --- a/app/assets/stylesheets/bootstrap.sass +++ b/app/assets/stylesheets/bootstrap.sass @@ -7,8 +7,10 @@ @include bootstrap-default-alert-colors .alert h4 border: 0px !important + background-color: #fff !important .alert - padding: 8px 35px 8px 14px + border-radius: 16px + padding: 8px 14px 8px 14px margin-bottom: 20px font-size: 15px .alert h4 diff --git a/app/assets/stylesheets/buttons.sass b/app/assets/stylesheets/buttons.sass index 276577616..833adb3f9 100644 --- a/app/assets/stylesheets/buttons.sass +++ b/app/assets/stylesheets/buttons.sass @@ -1,284 +1,786 @@ -/** buttons **/ +/** buttons - Updated to match Black Duck / OpenHub redesign system **/ .btn - font-family: 'Roboto' !important - font-weight: bold !important - display: inline-block - color: #FFF !important + font-family: 'Roboto', sans-serif !important + font-weight: 500 !important + display: inline-flex !important + align-items: center !important + justify-content: center !important + gap: 8px !important + color: #374151 !important background-image: none !important - border: 5px solid - border-radius: 0 - box-shadow: none !important - -webkit-transition: all ease .15s - -moz-transition: all ease .15s - -o-transition: all ease .15s - transition: all ease .15s - cursor: pointer - vertical-align: middle - margin: 0 - position: relative - padding: 0 12px 1px - line-height: 32px - font-size: 14px - -.btn-large - padding: 0 14px 1px - line-height: 38px - border-width: 6px - font-size: 16px - -.btn-small - padding: 0 5.5px - line-height: 24px - border-width: 4px - font-size: 15px - /*margin: 2.5px; + border: 1px solid !important + border-radius: 6px !important + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) !important + transition: all 0.2s ease !important + cursor: pointer !important + vertical-align: middle !important + margin: 0 !important + position: relative !important + padding: 8px 16px !important + line-height: normal !important + font-size: 14px !important + text-decoration: none !important + white-space: nowrap !important + +.btn-large, .btn-lg + padding: 10px 24px !important + font-size: 16px !important + +.btn-small, .btn-sm + padding: 6px 12px !important + font-size: 13px !important .btn-mini - padding: 0 5px - line-height: 22px - border-width: 2px - font-size: 12px + padding: 4px 8px !important + font-size: 12px !important .btn-minier - padding: 0 4px - line-height: 18px - border-width: 1px - font-size: 11px + padding: 2px 6px !important + font-size: 11px !important -button.btn:active - top: 1px - left: 1px +button.btn:active, a.btn:active + top: 0 !important + left: 0 !important .btn, .btn-default - @include default-button-colors + background-color: white !important + border-color: #e5e7eb !important + color: #374151 !important -.btn.active, .btn-default.active - background-color: #9baab3 !important - border-color: #8a9ba6 + &:hover + border-color: #d1d5db !important + background-color: #f9fafb !important -.btn.no-border.active, .btn-default.no-border.active - background-color: #92a3ac !important - border-color: #92a3ac + &:active + outline: none !important + box-shadow: none !important + border-color: #e5e7eb !important -.btn.disabled, .btn-default.disabled, .btn[disabled], .btn-default[disabled] - background-color: #abbac3 !important + &:focus:not(:focus-visible) + outline: none !important + box-shadow: none !important -.btn-header - width: 100px + &:focus-visible + outline: 2px solid #5A2A82 !important + outline-offset: 2px !important + box-shadow: 0 0 0 3px rgba(90, 42, 130, 0.25) !important + border-color: #5A2A82 !important -.i_use_this_btn - @include secondary-button-colors - width: 100% + &.active + background-color: #f3f4f6 !important + border-color: #d1d5db !important -.btn-info - @include info-button-colors - &.no-hover:hover - background-color: #0082C6 !important - &.active, &.no-border.active - background-color: #66B7E8 !important - border-color: #66B7E8 &.disabled, &[disabled] - background-color: #66CEF6 !important + background-color: #f9fafb !important + color: #9ca3af !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: #2D1548 !important + border-color: #5A2A82 !important + color: #d1d5db !important + + &:hover + border-color: #ffb91a !important + background-color: #2D1548 !important + + &.active + background-color: #1D0631 !important + border-color: #ffb91a !important + + &.disabled, &[disabled] + background-color: #1D0631 !important + color: #6b7280 !important + opacity: 0.6 !important .btn-primary - @include primary-button-colors - &.no-hover:hover - background-color: #005481 !important - &.active, &.no-border.active - background-color: #0082C6 !important - border-color: #005481 + background-color: #5A2A82 !important + border-color: #5A2A82 !important + color: white !important + font-weight: 600 !important + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23) !important + + &:hover + opacity: 0.9 !important + background-color: #5A2A82 !important + border-color: #5A2A82 !important + color: white !important + + &:active, &:focus + background-color: #4A1A72 !important + border-color: #4A1A72 !important + color: white !important + + &.active + background-color: #4A1A72 !important + border-color: #4A1A72 !important + + &.disabled, &[disabled] + background-color: #8A5AAA !important + border-color: #8A5AAA !important + color: white !important + opacity: 1 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: #ffb91a !important + border-color: #ffb91a !important + color: #1D0631 !important + box-shadow: 0 3px 6px rgba(0,0,0,0.32), 0 3px 6px rgba(0,0,0,0.46) !important + + &:hover + opacity: 0.9 !important + background-color: #ffb91a !important + border-color: #ffb91a !important + color: #1D0631 !important + + &:active, &:focus + background-color: #e6a617 !important + border-color: #e6a617 !important + color: #1D0631 !important + + &.active + background-color: #e6a617 !important + border-color: #e6a617 !important + + &.disabled, &[disabled] + background-color: #ffb91a !important + border-color: #ffb91a !important + color: #1D0631 !important + opacity: 0.7 !important + +.btn-secondary + background-color: white !important + border-color: #e5e7eb !important + color: #374151 !important + font-weight: 500 !important + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) !important + + &:hover + border-color: #5A2A82 !important + background-color: white !important + color: #374151 !important + + &:active, &:focus + background-color: white !important + border-color: #5A2A82 !important + color: #374151 !important + + &.active + background-color: #f9fafb !important + border-color: #5A2A82 !important + + &.disabled, &[disabled] + background-color: #f9fafb !important + color: #9ca3af !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: #2D1548 !important + border-color: #5A2A82 !important + color: #d1d5db !important + box-shadow: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) !important + + &:hover + border-color: #ffb91a !important + background-color: #2D1548 !important + color: #d1d5db !important + + &:active, &:focus + background-color: #2D1548 !important + border-color: #ffb91a !important + + &.active + background-color: #1D0631 !important + border-color: #ffb91a !important + + &.disabled, &[disabled] + background-color: #1D0631 !important + color: #6b7280 !important + opacity: 0.6 !important + +.btn-outline + background-color: transparent !important + border-color: #e5e7eb !important + color: #374151 !important + font-weight: 500 !important + + &:hover + background-color: #f9fafb !important + border-color: #d1d5db !important + + &:active, &:focus + background-color: #f3f4f6 !important + + &.disabled, &[disabled] + background-color: transparent !important + color: #9ca3af !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: transparent !important + border-color: #5A2A82 !important + color: #d1d5db !important + + &:hover + background-color: rgba(90, 42, 130, 0.15) !important + border-color: #ffb91a !important + + &:active, &:focus + background-color: rgba(90, 42, 130, 0.25) !important + + &.disabled, &[disabled] + background-color: transparent !important + color: #6b7280 !important + opacity: 0.6 !important .btn-refresh - @include secondary-button-colors + color: white !important + background-color: #5A2A82 !important + border-color: #5A2A82 !important + border-radius: 0 12px 12px 0 + margin-left: -1px + padding: 10px 18px + transition: all 0.15s ease-in-out + + &:hover + background-color: rgba(90, 42, 130, 0.9) !important + border-color: #5A2A82 !important + transform: none + + &:active + background-color: #4A1A72 !important + border-color: #4A1A72 !important + &.no-hover:hover - background-color: #0082C6 !important + background-color: #5A2A82 !important + &.active, &.no-border.active - background-color: #66B7E8 !important - border-color: #66B7E8 + background-color: #4A1A72 !important + border-color: #4A1A72 + &.disabled, &[disabled] - background-color: #66CEF6 !important + background-color: #8A5AAA !important + border-color: #8A5AAA !important + opacity: 0.6 + cursor: not-allowed + + i + font-size: 16px + line-height: 1 + + // Dark mode + .dark & + background-color: #ffb91a !important + border-color: #ffb91a !important + color: #1D0631 !important + + &:hover + background-color: rgba(255, 185, 26, 0.9) !important + border-color: #ffb91a !important + + &:active + background-color: #e6a617 !important + border-color: #e6a617 !important + + &.active, &.no-border.active + background-color: #e6a617 !important + border-color: #e6a617 + + &.disabled, &[disabled] + background-color: #ffb91a !important + border-color: #ffb91a !important + opacity: 0.4 + +// Modern "I Use This" Button Design - All variants +.i_use_this_btn, +.i-use-this-btn, +.i-use-this-btn-mobile, +.btn-use-this + padding: 6px 16px !important + background-color: #5A2A82 !important + color: #ffffff !important + border: none !important + border-radius: 8px !important + font-size: 12px !important + font-weight: 600 !important + cursor: pointer !important + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) !important + transition: background-color 0.2s ease !important + display: inline-flex !important + align-items: center !important + justify-content: center !important + + &:hover + background-color: rgba(90, 42, 130, 0.9) !important + color: #ffffff !important + + &:active, &:focus + background-color: rgba(90, 42, 130, 0.9) !important + color: #ffffff !important + + &.disabled, &[disabled] + background-color: #9ca3af !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + html.dark & + background-color: #ffb91a !important + color: #1D0631 !important + box-shadow: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) !important + + &:hover + background-color: rgba(255, 185, 26, 0.9) !important + color: #1D0631 !important + + &:active, &:focus + background-color: rgba(255, 185, 26, 0.9) !important + + &.disabled, &[disabled] + background-color: #6b7280 !important + opacity: 0.6 !important + +// State variants for redesigned button +.i-use-this-btn, +.i-use-this-btn-mobile + &.using + background-color: #5A2A82 !important + color: #ffffff !important + + &:hover + background-color: #4a1f6e !important + + html.dark & + background-color: #5A2A82 !important + color: #ffffff !important + + &:hover + background-color: #4a1f6e !important + + &.not-using + background-color: #ffb91a !important + color: #1D0631 !important + + &:hover + background-color: #ffcc4d !important + + html.dark & + background-color: #ffb91a !important + color: #1D0631 !important + + &:hover + background-color: #ffcc4d !important + +.btn-header + width: 100px + +.btn-info + background-color: #5A2A82 !important + border-color: #5A2A82 !important + color: white !important + font-weight: 600 !important + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) !important + + &:hover + opacity: 0.9 !important + background-color: #5A2A82 !important + border-color: #5A2A82 !important + color: white !important + + &:active, &:focus + background-color: #4A1A72 !important + color: white !important + border-color: #4A1A72 !important + + &.active + background-color: #4A1A72 !important + border-color: #4A1A72 !important -.btn-success - @include join-now-button-colors - &.no-border:hover - border-color: #659965 - &.no-hover:hover - background-color: #437417 !important - &.active, &.no-border.active - background-color: #79B247 !important - border-color: #79B247 &.disabled, &[disabled] - background-color: #79B247 !important - background-color: #FFB819 !important + background-color: #8A5AAA !important + border-color: #8A5AAA !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: #ffb91a !important + border-color: #ffb91a !important + color: #1D0631 !important + box-shadow: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) !important + + &:hover + background-color: #e6a617 !important + border-color: #e6a617 !important + color: #1D0631 !important + + &.disabled, &[disabled] + background-color: #ffb91a !important + opacity: 0.4 !important + +.btn-success + background-color: #10b981 !important + border-color: #10b981 !important + color: white !important + font-weight: 600 !important + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) !important + &:hover - background-color: #E07C05 !important - border-color: #E07C05 !important + opacity: 0.9 !important + background-color: #10b981 !important + border-color: #10b981 !important + color: white !important + + &:active, &:focus + background-color: #059669 !important + border-color: #059669 !important + + &.active + background-color: #059669 !important + border-color: #059669 !important + + &.disabled, &[disabled] + background-color: #6ee7b7 !important + border-color: #6ee7b7 !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: #10b981 !important + border-color: #10b981 !important + box-shadow: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) !important + + &:hover + background-color: #059669 !important + border-color: #059669 !important + + &.disabled, &[disabled] + background-color: #10b981 !important + opacity: 0.4 !important + .btn-warning - @include warning-button-colors - &.no-hover:hover - background-color: #9F4F0A !important - &.active, &.no-border.active - background-color: #F58220 !important - border-color: #F58220 + background-color: #f59e0b !important + border-color: #f59e0b !important + color: #1D0631 !important + font-weight: 600 !important + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) !important + + &:hover + opacity: 0.9 !important + background-color: #f59e0b !important + border-color: #f59e0b !important + color: #1D0631 !important + + &:active, &:focus + background-color: #d97706 !important + border-color: #d97706 !important + color: #1D0631 !important + + &.active + background-color: #d97706 !important + border-color: #d97706 !important + + &.disabled, &[disabled] + background-color: #fbbf24 !important + border-color: #fbbf24 !important + color: #1D0631 !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: #f59e0b !important + border-color: #f59e0b !important + color: #1D0631 !important + box-shadow: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) !important + + &:hover + background-color: #d97706 !important + border-color: #d97706 !important + color: #1D0631 !important + + &.disabled, &[disabled] + background-color: #f59e0b !important + color: #1D0631 !important + opacity: 0.4 !important .btn-danger - @include danger-button-colors - &.no-hover:hover - background-color: #9F2609 !important - &.active, &.no-border.active - background-color: #F5471D !important - border-color: #F5471D + background-color: #dc2626 !important + border-color: #dc2626 !important + color: white !important + font-weight: 600 !important + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23) !important + + &:hover + opacity: 0.9 !important + background-color: #dc2626 !important + border-color: #dc2626 !important + color: white !important + + &:active, &:focus + background-color: #b91c1c !important + border-color: #b91c1c !important + + &.active + background-color: #dc2626 !important + border-color: #dc2626 !important + &.disabled, &[disabled] - background-color: #F5471D !important + background-color: #fca5a5 !important + border-color: #fca5a5 !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: #b91c1c !important + border-color: #b91c1c !important + box-shadow: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) !important + + &:hover + background-color: #991b1b !important + border-color: #991b1b !important + &.disabled, &[disabled] + background-color: #ef4444 !important + opacity: 0.4 !important + +// Dark neutral (unchanged - still works with branding) .btn-inverse background-color: #575757 !important - border-color: #575757 + border-color: #575757 !important + color: white !important + font-weight: 600 !important + &:hover background-color: #303030 !important - &.no-border:hover - border-color: #303030 - &.no-hover:hover - background-color: #555555 !important - &.active + border-color: #303030 !important + + &.active, &.no-border.active background-color: #434343 !important border-color: #333333 - &.no-border.active - background-color: #3b3b3b !important - border-color: #3b3b3b + &.disabled, &[disabled] background-color: #555555 !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: #4b5563 !important + border-color: #4b5563 !important + color: white !important + &:hover + background-color: #374151 !important + border-color: #374151 !important + + &.disabled, &[disabled] + opacity: 0.4 !important + +// Muted purple accent (renamed intent: subtle brand accent) .btn-pink background-color: #7E7392 !important - border-color: #7E7392 + border-color: #7E7392 !important + color: white !important + font-weight: 600 !important + &:hover background-color: #53456D !important - &.no-border:hover - border-color: #b73766 - &.no-hover:hover - background-color: #d6487e !important - &.active - background-color: #c74072 !important - border-color: #b33564 - &.no-border.active - background-color: #be386a !important - border-color: #be386a + border-color: #53456D !important + + &.active, &.no-border.active + background-color: #53456D !important + border-color: #53456D + &.disabled, &[disabled] - background-color: #d6487e !important + background-color: #7E7392 !important + opacity: 0.6 !important + cursor: not-allowed !important + // Dark mode + .dark & + background-color: #7E7392 !important + border-color: #7E7392 !important + color: white !important + + &:hover + background-color: #53456D !important + border-color: #53456D !important + + &.disabled, &[disabled] + opacity: 0.4 !important + +// Teal accent (renamed intent: secondary brand color) .btn-purple background-color: #45867A !important - border-color: #45867A + border-color: #45867A !important + color: white !important + font-weight: 600 !important + &:hover background-color: #166859 !important - &.no-border:hover - border-color: #281649 - &.no-hover:hover - background-color: #281649 !important + border-color: #166859 !important + &.active, &.no-border.active - background-color: #7865A4 !important - border-color: #7865A4 + background-color: #166859 !important + border-color: #166859 + &.disabled, &[disabled] - background-color: #7865A4 !important + background-color: #45867A !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: #45867A !important + border-color: #45867A !important + color: white !important + &:hover + background-color: #166859 !important + border-color: #166859 !important + + &.disabled, &[disabled] + opacity: 0.4 !important + +// Neutral grey .btn-grey background-color: #797979 !important - border-color: #797979 + border-color: #797979 !important + color: white !important + font-weight: 600 !important + &:hover background-color: #575757 !important - &.no-border:hover - border-color: #575757 - &.no-hover:hover - background-color: #575757 !important + border-color: #575757 !important + &.active, &.no-border.active - background-color: #797979 !important - border-color: #797979 + background-color: #575757 !important + border-color: #575757 + &.disabled, &[disabled] background-color: #9A9A9A !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: #6b7280 !important + border-color: #6b7280 !important + color: white !important + + &:hover + background-color: #4b5563 !important + border-color: #4b5563 !important + &.disabled, &[disabled] + opacity: 0.4 !important + +// Soft accent (green-ish, used for subtle highlights) .btn-yellow background-color: #B3D084 !important - border-color: #B3D084 - color: #996633 !important + border-color: #B3D084 !important + color: #374151 !important + font-weight: 600 !important + &:hover background-color: #A0C465 !important - &.no-border:hover - border-color: #A0C465 - &.no-hover:hover - background-color: #A0C465 !important + border-color: #A0C465 !important + &.active, &.no-border.active - background-color: #B3D084 !important - border-color: #B3D084 + background-color: #A0C465 !important + border-color: #A0C465 + &.disabled, &[disabled] background-color: #B3D084 !important + opacity: 0.6 !important + cursor: not-allowed !important + + // Dark mode + .dark & + background-color: #B3D084 !important + border-color: #B3D084 !important + color: #1D0631 !important + &:hover + background-color: #A0C465 !important + border-color: #A0C465 !important + + &.disabled, &[disabled] + opacity: 0.4 !important + +// Light / muted (used for de-emphasized actions) .btn-light background-color: #DDD !important - border-color: #DDD + border-color: #DDD !important color: #888888 !important + font-weight: 500 !important + &:hover background-color: #BCBCBC !important - &.no-border:hover - border-color: #BCBCBC - &.no-hover:hover - background-color: #BCBCBC !important + border-color: #BCBCBC !important + &.active, &.no-border.active - background-color: #DDD !important - border-color: #DDD + background-color: #BCBCBC !important + border-color: #BCBCBC + &.disabled, &[disabled] background-color: #DDD !important + opacity: 0.6 !important + cursor: not-allowed !important + &.btn-mini:after left: -2px right: -2px top: -2px bottom: -2px + &.btn-small:after left: -4px right: -4px top: -4px bottom: -4px + &.btn-large:after left: -6px right: -6px top: -6px bottom: -6px + // Dark mode + .dark & + background-color: #374151 !important + border-color: #374151 !important + color: #9ca3af !important + + &:hover + background-color: #4b5563 !important + border-color: #4b5563 !important + + &.disabled, &[disabled] + opacity: 0.4 !important + .btn &.disabled.active, &[disabled].active, &.disabled:focus, &[disabled]:focus, &.disabled:active, &[disabled]:active - outline: none + outline: none !important + top: 0 !important + left: 0 !important + &.disabled:active, &[disabled]:active - top: 0 - left: 0 - &.active - color: #efe5b5 - &:after - display: inline-block - content: "" - position: absolute - border-bottom: 1px solid #efe5b5 - left: -4px - right: -4px - bottom: -4px - &.btn-small:after - left: -3px - right: -3px - bottom: -3px - border-bottom-width: 1px - &.btn-large:after - left: -5px - right: -5px - bottom: -5px - border-bottom-width: 1px - &.btn-mini:after, &.btn-minier:after - left: -1px - right: -1px - bottom: -1px - border-bottom-width: 1px - &.btn-yellow:after - border-bottom-color: #c96338 - &.btn-light - color: #515151 - &:after - border-bottom-color: #B5B5B5 + top: 0 !important + left: 0 !important + + &:hover + top: 0 !important + left: 0 !important diff --git a/app/assets/stylesheets/charts.sass b/app/assets/stylesheets/charts.sass index 9de5968b6..4702fda0f 100644 --- a/app/assets/stylesheets/charts.sass +++ b/app/assets/stylesheets/charts.sass @@ -1,26 +1,3 @@ -.watermark340 .highcharts-container - background: asset-url("charts/watermark_340.png") no-repeat center - background-size: contain - -.watermark440 .highcharts-container - background: asset-url("charts/watermark_440.png") no-repeat center - background-size: contain - -.watermark692 .highcharts-container - background: asset-url("charts/watermark_692.png") no-repeat center - background-size: contain - -.watermark860 .highcharts-container - background: asset-url("charts/watermark_860.png") no-repeat center - background-size: contain - -.watermark900white .highcharts-container - background: asset-url("charts/watermark_white_900.png") no-repeat center - background-size: contain - -.watermark914 .highcharts-container - background: asset-url("charts/watermark_914.png") no-repeat center - background-size: contain #vulnerabilities_index_page, #vulnerability_version_chart @@ -197,133 +174,182 @@ fill: none stroke-width: 2px - /* Tooltip */ - .highcharts-tooltip - cursor: default - pointer-events: none - white-space: nowrap - transition: stroke 150ms - /* font-size: 11pt - /* font-weight: normal - background: rgba(255,255,255,0.85) - border: 1px solid #000 - border-radius: 3px - box-shadow: 1px 1px 2px #888 - padding: 8px - tspan - stroke: black - stroke-width: 0px - opacity: 1.0 - z-index: 3 - - .highcharts-tooltip text - fill: #333333 - - .highcharts-tooltip .highcharts-header - font-size: 0.85em - - .highcharts-tooltip-box - stroke-width: 1px - fill: #f7f7f7 - fill-opacity: 0.85 - - .highcharts-tooltip-box .highcharts-label-box - fill: #f7f7f7 - fill-opacity: 0.85 - - div.highcharts-tooltip - filter: none - .highcharts-point-inactive opacity: 1.0 + .highcharts-series .highcharts-point + stroke: rgba(255, 255, 255, 0.6) + + .highcharts-legend-item .highcharts-point + stroke-width: 0 + .highcharts-point.highcharts-color-0, .highcharts-legend-item.highcharts-color-0 .highcharts-point, .highcharts-tooltip .highcharts-color-0 .highcharts-color-0, .highcharts-pointhighcharts-color-0 - @include projects-demographics-inactive-fill + fill: #64748b .highcharts-tooltip.highcharts-color-0, .highcharts-data-label-connector.highcharts-color-0 - @include projects-demographics-inactive-stroke + stroke: #64748b .highcharts-point.highcharts-color-1, .highcharts-legend-item.highcharts-color-1 .highcharts-point, .highcharts-tooltip .highcharts-color-1 - @include projects-demographics-very-low-fill - - .highcharts-halo.highcharts-color-1 - @include projects-demographics-very-low-fill - opacity: 0.3 + fill: #06b6d4 .highcharts-tooltip.highcharts-color-1, .highcharts-data-label-connector.highcharts-color-1 - @include projects-demographics-very-low-stroke + stroke: #06b6d4 .highcharts-point.highcharts-color-2, .highcharts-legend-item.highcharts-color-2 .highcharts-point, .highcharts-tooltip .highcharts-color-2 - @include projects-demographics-low-fill - - .highcharts-halo.highcharts-color-2 - @include projects-demographics-low-fill - opacity: 0.3 + fill: #3b82f6 .highcharts-tooltip.highcharts-color-2, .highcharts-data-label-connector.highcharts-color-2 - @include projects-demographics-low-stroke + stroke: #3b82f6 .highcharts-point.highcharts-color-3, .highcharts-legend-item.highcharts-color-3 .highcharts-point, .highcharts-tooltip .highcharts-color-3 - @include projects-demographics-moderate-fill - - .highcharts-halo.highcharts-color-3 - @include projects-demographics-moderate-fill - opacity: 0.3 + fill: #10b981 .highcharts-tooltip.highcharts-color-3, .highcharts-data-label-connector.highcharts-color-3 - @include projects-demographics-moderate-stroke + stroke: #10b981 .highcharts-point.highcharts-color-4, .highcharts-legend-item.highcharts-color-4 .highcharts-point, .highcharts-tooltip .highcharts-color-4 - @include projects-demographics-high-fill - - .highcharts-halo.highcharts-color-4 - @include projects-demographics-high-fill - opacity: 0.3 + fill: #f59e0b .highcharts-tooltip.highcharts-color-4, .highcharts-data-label-connector.highcharts-color-4 - @include projects-demographics-high-stroke + stroke: #f59e0b .highcharts-point.highcharts-color-5, .highcharts-legend-item.highcharts-color-5 .highcharts-point, .highcharts-tooltip .highcharts-color-5 - @include projects-demographics-very-high-fill - - .highcharts-halo.highcharts-color-5 - @include projects-demographics-very-high-fill - opacity: 0.3 + fill: #ef4444 .highcharts-tooltip.highcharts-color-5, .highcharts-data-label-connector.highcharts-color-5 - @include projects-demographics-very-high-stroke + stroke: #ef4444 .highcharts-point.highcharts-color-6, .highcharts-legend-item.highcharts-color-6 .highcharts-point, .highcharts-tooltip .highcharts-color-6 - @include projects-demographics-new-fill - - .highcharts-halo.highcharts-color-6 - @include projects-demographics-new-fill - opacity: 0.3 + fill: #a855f7 .highcharts-tooltip.highcharts-color-6, .highcharts-data-label-connector.highcharts-color-6 - @include projects-demographics-new-stroke + stroke: #a855f7 + +// styledMode: true means all label/legend styling must come from CSS, +// not from Highcharts JS options (itemStyle, color, etc. are ignored). +#demographics_chart + .highcharts-data-label text + fill: #1a202c + + @media (max-width: 768px) + .highcharts-legend-item text + font-size: 11px !important + + @media (max-width: 640px) + .highcharts-legend-item text + font-size: 10px !important + +html.dark #demographics_chart + .highcharts-data-label text + fill: #e2e8f0 !important + + .highcharts-data-label-connector + stroke: #94a3b8 !important + + .highcharts-legend-item text + fill: #e2e8f0 !important + + .highcharts-background + fill: #2D1548 + +// Demographics tooltip — rendered outside #demographics_chart via outside: true. +// All visual styling targets the HTML span rendered by useHTML: true. +// The SVG rect must be fully hidden to prevent double-layer background. +.highcharts-tooltip-container + z-index: 9999 !important + +// Kill every possible SVG background layer Highcharts injects for the demographics tooltip. +// With styledMode: true + outside: true, Highcharts creates a inside the tooltip +// container with its own injected by explore.js.coffee +#pai-global-tooltip + display: none + position: fixed + background: #111827 + color: white + border: 1px solid #374151 + border-radius: 8px + padding: 8px 12px + font-size: 12px + font-weight: 500 + white-space: nowrap + z-index: 9999 + box-shadow: 0 20px 25px -5px rgba(0,0,0,0.25), 0 10px 10px -5px rgba(0,0,0,0.15) + pointer-events: none + // Arrow pointing down toward the PAI icon + &::after + content: '' + position: absolute + top: 100% + left: 50% + transform: translateX(-50%) + border: 5px solid transparent + border-top-color: #374151 + &.visible + display: block diff --git a/app/assets/stylesheets/footer.sass b/app/assets/stylesheets/footer.sass index dccca4602..61f45a71a 100644 --- a/app/assets/stylesheets/footer.sass +++ b/app/assets/stylesheets/footer.sass @@ -1,81 +1,154 @@ footer - @include footer-colors + // Figma design: purple gradient background + position: relative + overflow: hidden + background: linear-gradient(to right, #1D0631, #5A2A82) !important + color: white !important + margin: 0 + padding: 0 + width: 100% + max-width: 100% + // Matches Figma: max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 + .footer-container + position: relative + max-width: 1280px + margin: 0 auto + padding: 24px 16px + @media (min-width: 640px) + padding: 32px 24px + @media (min-width: 1024px) + padding: 48px 32px - margin: 20px 0 0 0 - min-height: 250px - padding-top: 10px - width: 960px - - @media (min-width: 320px) and (max-width: 480px) - width: auto - min-height: 194px !important - padding-top: 0px !important - margin: 0px 0 0 0 !important - .footer-left - margin-left: 5px !important - margin-top: 20px !important - min-height: 150px !important - .footer-mid - line-height: 15px - margin-left: 30px !important - font-size: 8px !important - width: 32% !important - .footer-right - font-size: 8px !important - padding-right: 2px !important - width: 31% !important - .footer-bottom - margin-left: 0px !important - margin-top: 0px !important - @media (min-width: 540px) and (max-width: 1024px) - width: auto + // Matches Figma: grid-cols-1 md:grid-cols-3 gap-6 sm:gap-8 md:gap-12 + .footer-grid + display: grid + grid-template-columns: 1fr + gap: 24px + margin-bottom: 24px + @media (min-width: 640px) + gap: 32px + margin-bottom: 32px + @media (min-width: 768px) + grid-template-columns: repeat(3, 1fr) + gap: 48px + margin-bottom: 48px + // Matches Figma: h-8 sm:h-10 .footer-left - float: left - margin-top: 20px - margin-left: 30px - width: 15% + display: flex + align-items: flex-start + justify-content: center + @media (min-width: 768px) + justify-content: flex-start a .logo_img - width: 100% - height: 100% + width: auto + height: 32px + object-fit: contain + @media (min-width: 640px) + height: 40px - .footer-mid - float: left - margin-top: 20px - margin-left: 120px - margin-right: 40px - width: 20% - font-size: 12px - a - @include footer-colors - padding: 4px 4px - - .footer-right - float: left - margin-top: 20px - margin-bottom: 20px - width: 30% - font-size: 12px - padding-right: 60px - text-align: left + // Matches Figma: text-xs sm:text-sm for headers and links + .footer-mid, .footer-right + h3 + font-size: 12px + font-weight: 600 + color: white !important + margin-bottom: 8px + text-transform: uppercase + letter-spacing: 0.05em + @media (min-width: 640px) + font-size: 14px + margin-bottom: 12px + @media (min-width: 768px) + margin-bottom: 16px + ul + list-style: none + margin: 0 + padding: 0 + li + margin-bottom: 6px + @media (min-width: 640px) + margin-bottom: 8px + @media (min-width: 768px) + margin-bottom: 10px + p + margin: 0 0 6px 0 + @media (min-width: 640px) + margin: 0 0 8px 0 + @media (min-width: 768px) + margin: 0 0 10px 0 a - @include footer-colors - padding: 4px 6px + color: #d1d5db !important + text-decoration: none + font-size: 12px + font-weight: 400 + transition: color 0.3s ease + @media (min-width: 640px) + font-size: 14px + &:hover + color: white !important + // Matches Figma: py-3 sm:py-4 md:py-6 .footer-bottom - @include footer-copyright-colors - - float: left - margin-top: 10px - margin-left: 30px - margin-bottom: 2em - bottom: 0 - a - @include footer-copyright-colors + border-top: 1px solid rgba(255, 255, 255, 0.1) + padding: 12px 0 + @media (min-width: 640px) + padding: 16px 0 + @media (min-width: 768px) + padding: 24px 0 + .footer-bottom-content + display: flex + flex-direction: column + align-items: center + justify-content: space-between + gap: 12px + @media (min-width: 640px) + flex-direction: row + // Matches Figma: text-[10px] sm:text-xs + .copyright + color: #9ca3af !important + font-size: 10px + order: 2 + @media (min-width: 640px) + font-size: 12px + order: 1 + .follow-us + display: flex + align-items: center + gap: 8px + order: 1 + @media (min-width: 640px) + order: 2 + // Matches Figma: text-xs sm:text-sm + span + color: #d1d5db !important + font-size: 12px + font-weight: 500 + @media (min-width: 640px) + font-size: 14px + // Matches Figma: w-8 h-8 + a + display: flex + align-items: center + justify-content: center + width: 32px + height: 32px + background-color: rgba(255, 255, 255, 0.1) + border-radius: 8px + transition: background-color 0.3s ease + &:hover + background-color: rgba(255, 255, 255, 0.2) + i + color: #d1d5db + font-size: 16px + transition: color 0.3s ease + &:hover i + color: white footer.fluid_footer width: auto + background: linear-gradient(to right, #1D0631, #5A2A82) !important .footer-left width: 12% ul @@ -83,16 +156,22 @@ footer.fluid_footer a padding: 1rem 2rem font-weight: bold + color: rgba(209, 213, 219, 1) !important + &:hover + color: white !important .footer-mid width: 25% text-align: left - font-weight: bold + font-weight: 600 font-size: 13px + color: white !important .footer-right width: 40% float: left - font-weight: bold + font-weight: 600 font-size: 13px + color: white !important .footer-bottom float: left margin-botton: 2em !important + color: rgba(156, 163, 175, 1) !important diff --git a/app/assets/stylesheets/forums_topics_posts.sass b/app/assets/stylesheets/forums_topics_posts.sass index f989b3334..daa8dd3d9 100644 --- a/app/assets/stylesheets/forums_topics_posts.sass +++ b/app/assets/stylesheets/forums_topics_posts.sass @@ -58,7 +58,7 @@ color: white text-decoration: none -.well.posts +.oh-card.posts border-radius: none border: none background-color: black @@ -72,7 +72,7 @@ #post_body_text_size font-size: 13px -.well.posts ul li +.oh-card.posts ul li margin-left: 20px .editor-preview.editor-preview-active li diff --git a/app/assets/stylesheets/grid.sass b/app/assets/stylesheets/grid.sass index c805b2ff6..f03461444 100644 --- a/app/assets/stylesheets/grid.sass +++ b/app/assets/stylesheets/grid.sass @@ -2,7 +2,7 @@ padding-top: 1.5em .soft - color: #777 + color: #595959 .table.unstyled margin: 0 diff --git a/app/assets/stylesheets/home.sass b/app/assets/stylesheets/home.sass index ce9fbeac6..21ee98342 100644 --- a/app/assets/stylesheets/home.sass +++ b/app/assets/stylesheets/home.sass @@ -1,159 +1,143 @@ -.showcase - padding-top: 20px - padding-bottom: 30px - width: 980px - margin-left: -15px - padding-left: 5px - padding-right: 5px -@media only all and (min-width: 320px) and (max-width: 480px) - .showcase - width: auto - margin-left: 0px - .billboard - height: 185px !important - -.billboard - @include billboard-background-color - color: white - height: 160px - padding-top: 30px - padding-bottom: 30px - text-align: center +// ======================================== +// Hero Section - Figma Design System +// ======================================== +// Main container with consistent 1280px max-width throughout +// Isolated from global body font-size: 1.3rem override + +// Common section background +.section_bg + background: #f9fafb !important + transition: background 0.3s ease + html.dark & + background: #1D0631 !important + +.hero_section + @extend .section_bg + padding: 0 16px 4px + box-sizing: border-box + @media (min-width: 640px) + padding: 0 24px 16px + @media (min-width: 1024px) + padding: 0 32px 16px + // Dark mode support + html.dark & + background: #1D0631 !important + // Ensure all child elements use border-box + *, *::before, *::after + box-sizing: border-box + +// Hero Container - Controls all child widths consistently (1280px) +.hero_container + max-width: 1280px + margin: 0 auto width: 100% - z-index: 2 - .billboard_search - // margin-top: 80px - .discover - padding: 0 - .discover_msg - font-size: 2.1em !important - font-weight: 400!important - margin-top: -10px + box-sizing: border-box + +// Hero Header - Figma Specs +.hero_header + text-align: center + margin-bottom: 24px + padding: 0 + @media (min-width: 768px) margin-bottom: 24px - h1 - line-height: 1.2em !important -@media only all and (min-width: 320px) and (max-width: 480px) - .billboard - .discover_msg - font-size: 13px !important - #global_statistics - p - font-size: 10px !important -@media (min-width: 540px) and (max-width: 1024px) - .showcase - width: auto - margin-left: 0px -.global_stats - margin-top: 25px -#icon_text - font-size: 1.6em - color: #646E81 - margin-left: -27px - cursor: pointer + +.hero_title + font-size: 1.875rem + font-weight: 700 + color: #5A2A82 + margin-bottom: 8px + line-height: 2.25rem + transition: color 0.3s ease + @media (min-width: 640px) + font-size: 2.25rem + line-height: 2.5rem + @media (min-width: 768px) + font-size: 48px + line-height: 1 + // Dark mode support + html.dark & + color: #ffffff + +.hero_subtitle + font-size: 0.875rem + color: #6b7280 + margin: 0 auto + max-width: 672px + line-height: 1.25rem + transition: color 0.3s ease + @media (min-width: 640px) + font-size: 16px + line-height: 1.5rem + // Dark mode support + html.dark & + color: #9ca3af + +// Search Container - Narrower for focused search UX (768px) +.search_container + max-width: 768px + margin: 0 auto 20px + width: 100% + box-sizing: border-box + +// Rotating Stats - Figma Animation +.rotating_stats + text-align: center + margin-top: 12px + margin-bottom: 24px + height: 20px #global_statistics - padding-top: 1px p - margin-top: -20px - text-align: center - font-size: 14px -.join_now - @include join-now-header-colors - padding-bottom: 5px - padding-left: 12px -.join_now_home - padding-top: 10px - font-size: 1.4em - p - font-size: 14px !important - .join_now_color - @include join-now-section-colors - li - font-size: 0.5em !important - height: 30px - width: 30px -.home_page_row - .whats_new - overflow: hidden - .col-md-8 - h2 - font-weight: 500 !important - margin-right: -40px !important - margin-left: 30px !important - text-indent: -10px !important - .col-md-4 - @include site-well-color - padding-bottom: 20px - h2 - font-weight: 500 !important - margin-left: 5px !important - text-indent: -10px !important - -@media only all and (min-width: 320px) and (max-width: 480px) - .home_page_row - .col-md-8 - h2 - margin-left: 0px !important - a - img - width: 100% - margin-left: 0px !important - .for_search_all_code - width: auto !important - .join_now_home - padding-top: 5px font-size: 12px - li - height: 10px - width: 10px -@media (min-width: 540px) and (max-width: 1024px) - .home_page_row - .col-md-8 - h2 - margin-left: 0px !important - a - img - width: 100% - margin-left: 0px !important -.btn.btn_join_now - @include join-now-button-colors - font-family: 'Roboto' !important - font-size: 22px !important - font-weight: bold !important - margin-left: 60px !important - width: 161px !important -::-webkit-input-placeholder - @include site-placeholder-color -\:-moz-placeholder - @include site-placeholder-color - opacity: 1 -::-moz-placeholder - @include site-placeholder-color - opacity: 1 -\:-ms-input-placeholder - @include site-placeholder-color - -input:focus::-webkit-input-placeholder - color: transparent -input:focus::-moz-placeholder - color: transparent -input:focus::-ms-input-placeholder - color: transparent -@media only all and (min-width: 320px) and (max-width: 480px) - .btn.btn_join_now - margin-left: 0px !important - width: 135px !important - font-size: 15px !important + font-weight: 500 + background: linear-gradient(90deg, #5A2A82 0%, #5A2A82 100%) + -webkit-background-clip: text + -webkit-text-fill-color: transparent + background-clip: text + margin: 0 + line-height: 1.5 + transition: opacity 0.3s ease + &.hide + opacity: 0 + display: block + &:not(.hide) + opacity: 1 + display: block + // Dark mode support + html.dark & + background: linear-gradient(90deg, #ffffff 0%, #ffffff 100%) !important + -webkit-background-clip: text !important + -webkit-text-fill-color: transparent !important + background-clip: text !important +// Old join_now and home_page_row styles removed - using new card-based design +// Old placeholder and btn_join_now styles removed - handled in new components .landing + @extend .section_bg margin-top: 20px - margin-left: -14px + margin-left: 0 + padding: 32px 16px + width: 100% + @media (min-width: 640px) + padding: 32px 24px + @media (min-width: 1024px) + padding: 32px 32px + // Dark mode support + html.dark & + background: #1D0631 !important .top_ten_main padding-left: 40px !important padding-bottom: 6px !important + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" .top_ten_row padding-left: 9px !important padding-bottom: 15px + transition: all 0.3s ease + border-radius: 8px + margin-bottom: 12px + &:hover + background: rgba(90, 42, 130, 0.05) + transform: translateX(4px) .top_ten_bar line-height: 0.6em width: 30% @@ -162,11 +146,31 @@ input:focus::-ms-input-placeholder .top_ten_label line-height: 1.5em padding-left: 8px - color: #555 + color: #6b7280 + font-size: 12px + font-weight: 500 + transition: color 0.3s ease + @media (min-width: 640px) + font-size: 13px + @media (min-width: 768px) + font-size: 14px + // Dark mode support + html.dark & + color: #9ca3af .top_ten h2 - font-weight: 500 !important + font-weight: 600 !important font-size: 16px !important + color: #111827 + line-height: 1.2 + transition: color 0.3s ease + @media (min-width: 640px) + font-size: 18px !important + @media (min-width: 768px) + font-size: 20px !important + // Dark mode support + html.dark & + color: #ffffff .top_ten.middle @include site-well-color .top_ten.last @@ -174,8 +178,8 @@ input:focus::-ms-input-placeholder margin-right: -25px !important .top_ten_icon img - background-color: #EEE - color: #000 + background-color: #f3f4f6 + color: #111827 font-size: 4px line-height: 32px width: 32px @@ -184,49 +188,46 @@ input:focus::-ms-input-placeholder float: left margin-bottom: 0 margin-top: 0 - margin-right: 2px + margin-right: 8px + border-radius: 8px + border: 1px solid #e5e7eb + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) -webkit-filter:grayscale(1) filter: grayscale(1) - opacity: .75 + opacity: .85 + transition: all 0.3s ease + // Dark mode support + html.dark & + background-color: rgba(255, 255, 255, 0.1) + border-color: #4b5563 + &:hover img + opacity: 1 + transform: scale(1.05) a.top_ten_icon p opacity: .75 background-color: #999 -.for_search_all_code - @include site-placeholder-color - height: 23px !important - // margin-top: -108px - width: 413px +// Old .for_search_all_code removed - using new .search_input instead .top_ten_link a border: 0 -.icon_search - font-size: 14px - font-weight: 300 - margin-top: -37px - margin-left: -71px - text-align: center -.wh_new - @include whats-new-header-colors - width: 100% - padding-bottom: 5px - -.whats-new-banner - display: block - width: 100% - margin-left: 0 !important - - img - max-width: 100% - height: auto - display: block - margin-left: 20px !important - margin-top: 0 !important -.common - padding-left: 6px - padding-bottom: 3px - font-size: 1.4em - font-weight: normal - color: #000000 + color: #5A2A82 + font-weight: 600 + font-size: 14px + text-decoration: none + transition: all 0.3s ease + @media (min-width: 640px) + font-size: 15px + @media (min-width: 768px) + font-size: 16px + &:hover + color: #1D0631 + text-decoration: none + // Dark mode support + html.dark & + color: #ffffff + &:hover + color: #ffb91a +// Old .icon_search, .wh_new, .common removed - using new card-based design .most_popular_projects @include most-popular-projects-colors .most_active_projects @@ -236,8 +237,6 @@ a.top_ten_icon p @include most-active-contributors-colors .popular @include most-popular-projects-background-color -.active - @include most-active-projects-background-color .contributor @include most-active-contributors-background-color .most_recent_projects @@ -251,4 +250,1343 @@ button #no-background background-color: white !important +// Feature Cards Section - Figma: 1024px width +.features_section + max-width: 1024px + margin: 0 auto 24px + box-sizing: border-box + @media (min-width: 768px) + display: grid + grid-template-columns: repeat(3, 1fr) + gap: 20px + +.feature_card + text-align: center + margin-bottom: 16px + @media (min-width: 768px) + margin-bottom: 0 + +.feature_icon + width: 40px + height: 40px + border-radius: 12px + display: flex + align-items: center + justify-content: center + margin: 0 auto 12px + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) + @media (min-width: 640px) + width: 48px + height: 48px + i + font-size: 20px + color: white + @media (min-width: 640px) + font-size: 24px + +.feature_icon_yellow + background: linear-gradient(135deg, #facc15 0%, #f97316 100%) + +.feature_icon_blue + background: linear-gradient(135deg, #3b82f6 0%, #4f46e5 100%) + +.feature_icon_green + background: linear-gradient(135deg, #10b981 0%, #14b8a6 100%) + +.feature_title + font-size: 12px !important + font-weight: 600 + color: #111827 + margin-bottom: 4px + line-height: 1.3 + transition: color 0.3s ease + @media (min-width: 640px) + font-size: 14px !important + // Dark mode support + html.dark & + color: #ffffff + +.feature_description + font-size: 10px !important + color: #6b7280 + line-height: 1.625 + padding: 0 8px + transition: color 0.3s ease + @media (min-width: 640px) + font-size: 12px !important + // Dark mode support + html.dark & + color: #9ca3af + +// Quick Stats Bar - Compact and centered (768px) +.quick_stats_bar + display: grid + grid-template-columns: repeat(2, 1fr) + gap: 16px + max-width: 768px + margin: 0 auto + padding: 16px + background: linear-gradient(90deg, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0.6) 50%, rgba(255,255,255,0.8) 100%) + backdrop-filter: blur(4px) + border-radius: 12px + border: 1px solid rgba(229, 231, 235, 0.6) + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) + transition: all 0.3s ease + box-sizing: border-box + @media (min-width: 768px) + display: flex + align-items: center + justify-content: center + gap: 24px + padding: 12px 24px + // Dark mode support - Yellow background with purple text + html.dark & + background: linear-gradient(90deg, #2D1548 0%, #2D1548 50%, #2D1548 100%) + border-color: rgba(255, 255, 255, 0.1) + + +.stat_item + text-align: center + +.stat_value + font-size: 16px + font-weight: 700 + color: #5A2A82 + line-height: 1.2 + transition: color 0.3s ease + @media (min-width: 640px) + font-size: 18px + @media (min-width: 768px) + font-size: 20px + // Dark mode - Yellow text on purple background + html.dark & + color: #ffb91a + + +.stat_label + font-size: 8px + color: #6b7280 + text-transform: uppercase + font-weight: 500 + letter-spacing: 0.05em + margin-top: 2px + transition: color 0.3s ease + @media (min-width: 640px) + font-size: 9px + @media (min-width: 768px) + font-size: 10px + // Dark mode - Gray text on purple background + html.dark & + color: #9ca3af + + +.stat_divider + display: none + transition: background 0.3s ease + @media (min-width: 768px) + display: block + width: 1px + height: 32px + background: #9ca3af + opacity: 1 + // Dark mode - White divider with low opacity on purple background + html.dark & + @media (min-width: 768px) + background: #ffffff + opacity: 0.2 + + +// Content Section +.content_section + @extend .section_bg + padding: 32px 0 + // Dark mode support + html.dark & + background: #1D0631 !important + + .container + max-width: 1280px + margin: 0 auto + padding: 0 16px + @media (min-width: 640px) + padding: 0 24px + @media (min-width: 1024px) + padding: 0 32px + +// Content Grid - matches Figma design: grid md:grid-cols-[35%_65%] gap-6 +.content_grid + display: grid + grid-template-columns: 1fr + gap: 24px + @media (min-width: 768px) + grid-template-columns: 35% 65% + +.contributors_column + width: 100% + +.whats_new_column + width: 100% + display: block + +.whats_new_join_stack + display: flex + flex-direction: column + gap: 24px + +// Top Contributors Card - Matches ContributorJourney.tsx +.top_contributors_card + background: white + border-radius: 16px + overflow: hidden + margin-bottom: 4px + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) + transition: box-shadow 0.2s + display: flex + flex-direction: column + &:hover + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23) + html.dark & + background: #2D1548 + box-shadow: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) + &:hover + box-shadow: 0 3px 6px rgba(0,0,0,0.32), 0 3px 6px rgba(0,0,0,0.46) + + +// Card Header +.top_contributors_card .card_header + display: flex + align-items: flex-start + gap: 16px + padding: 24px 24px 16px 24px + background: white + html.dark & + background: #2D1548 + +.top_contributors_card .header_icon + display: flex + align-items: center + justify-content: center + width: 40px + height: 40px + border-radius: 12px + background: rgba(90, 42, 130, 0.1) + flex-shrink: 0 + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) + @media (min-width: 640px) + width: 48px + height: 48px + html.dark & + background: linear-gradient(135deg, #1D0631 0%, #5A2A82 100%) + + i + color: #5A2A82 + font-size: 20px + @media (min-width: 640px) + font-size: 24px + html.dark & + color: white + +.top_contributors_card .header_content + flex: 1 + +.top_contributors_card .card_title + font-size: 16px + font-weight: 700 + color: #5A2A82 + margin: 0 0 6px 0 + line-height: 1.4 + @media (min-width: 640px) + font-size: 18px + html.dark & + color: white + +.top_contributors_card .card_subtitle + font-size: 10px + color: #5A2A82 + margin: 0 + line-height: 1.5 + @media (min-width: 640px) + font-size: 12px + html.dark & + color: #9CA3AF + +// Contributors List +.top_contributors_card .contributors_list + padding: 16px + display: flex + flex-direction: column + gap: 8px + max-height: 300px + overflow-y: scroll + scrollbar-width: thin + scrollbar-color: rgba(90, 42, 130, 0.4) rgba(90, 42, 130, 0.1) + @media (min-width: 768px) + max-height: 500px + &::-webkit-scrollbar + width: 8px + &::-webkit-scrollbar-track + background: rgba(90, 42, 130, 0.1) + border-radius: 3px + &::-webkit-scrollbar-thumb + background: rgba(90, 42, 130, 0.4) + border-radius: 3px + &:hover + background: rgba(90, 42, 130, 0.6) + html.dark & + scrollbar-color: rgba(255, 185, 26, 0.4) rgba(255, 185, 26, 0.1) + &::-webkit-scrollbar-track + background: rgba(255, 185, 26, 0.1) + &::-webkit-scrollbar-thumb + background: rgba(255, 185, 26, 0.4) + &:hover + background: rgba(255, 185, 26, 0.6) + +.top_contributors_card .contributor_item + display: flex + align-items: center + gap: 12px + padding: 12px + background: linear-gradient(to right, #f9fafb 0%, #f1f5f9 100%) + border: 1px solid #e5e7eb + border-radius: 12px + transition: all 0.2s + text-decoration: none + cursor: pointer + + &:hover + background: linear-gradient(to right, #f5f3ff 0%, #ede9fe 100%) + border-color: #5A2A82 + + html.dark & + background: linear-gradient(to right, rgba(55, 65, 81, 0.5) 0%, rgba(71, 85, 105, 0.5) 100%) + border-color: #4B5563 + &:hover + background: linear-gradient(to right, rgba(109, 40, 217, 0.3) 0%, rgba(124, 58, 237, 0.3) 100%) + border-color: #5A2A82 + +// All contributors visible on mobile to enable scrolling +// Mobile users can scroll to see all contributors + +// Avatar +.top_contributors_card .contributor_avatar + flex-shrink: 0 + width: 40px + height: 40px + +.top_contributors_card .avatar_img, +.top_contributors_card .avatar_placeholder + width: 40px + height: 40px + border-radius: 12px + object-fit: cover + display: block + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) + border: 2px solid #9CA3AF + +// Contributor Info +.top_contributors_card .contributor_info + flex: 1 + min-width: 0 + display: flex + flex-direction: column + gap: 2px + +.top_contributors_card .contributor_name_row + display: flex + align-items: center + gap: 8px + margin-bottom: 2px + +.top_contributors_card .contributor_name + font-size: 14px + font-weight: 600 + color: #5A2A82 + line-height: 1.3 + white-space: nowrap + overflow: hidden + text-overflow: ellipsis + html.dark & + color: white + +.top_contributors_card .contributor_rank + font-size: 9px + font-weight: 400 + color: rgba(90, 42, 130, 0.6) + text-transform: uppercase + letter-spacing: 0.05em + white-space: nowrap + flex-shrink: 0 + html.dark & + color: #9CA3AF + +.top_contributors_card .contributor_stats + display: flex + align-items: center + gap: 12px + font-size: 10px + color: rgba(90, 42, 130, 0.7) + + html.dark & + color: #9CA3AF + + span + display: flex + align-items: center + gap: 4px + + i + flex-shrink: 0 + display: inline-flex + align-items: center + justify-content: center + font-size: 9px + color: rgba(90, 42, 130, 0.7) + html.dark & + color: #9CA3AF + +.top_contributors_card .stat_commits, +.top_contributors_card .stat_projects + font-weight: 400 + color: rgba(90, 42, 130, 0.7) + html.dark & + color: #9CA3AF + +.top_contributors_card .stat_separator + display: none + +// Arrow +.top_contributors_card .contributor_arrow + flex-shrink: 0 + color: rgba(90, 42, 130, 0.4) + display: flex + align-items: center + transition: color 0.2s + + i + font-size: 16px + + html.dark & + color: rgba(255, 255, 255, 0.3) + +.top_contributors_card .contributor_item:hover .contributor_arrow + color: #5A2A82 + html.dark & + color: white + +// View All Footer +.top_contributors_card .view_all_footer + padding: 16px + +.top_contributors_card .view_all_link + display: flex + width: 100% + align-items: center + justify-content: center + gap: 8px + padding: 12px + font-size: 14px + font-weight: 600 + color: #5A2A82 + text-decoration: none + border: 2px solid rgba(90, 42, 130, 0.2) + border-radius: 12px + margin-top: 8px + transition: all 0.2s + cursor: pointer + background: transparent + + &:hover + color: #1D0631 + border-color: #5A2A82 + background: rgba(90, 42, 130, 0.05) + + i + font-size: 16px + + html.dark & + color: #ffb91a !important + border-color: #ffb91a + &:hover + background: #ffb91a + color: #1D0631 !important + + +// What's New Card - Figma Design +.whats_new_card + background: white + border-radius: 16px + overflow: hidden + height: 100% + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) + transition: box-shadow 0.2s + &:hover + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23) + html.dark & + background: #2D1548 + box-shadow: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) + &:hover + box-shadow: 0 3px 6px rgba(0,0,0,0.32), 0 3px 6px rgba(0,0,0,0.46) + + +.card_header_section + padding: 16px 16px 12px 16px !important + background: white + @media (min-width: 640px) + padding: 24px 24px 16px 24px !important + // Dark mode support + html.dark & + background: #2D1548 + +.whats_new_title + font-size: 18px !important + font-weight: 700 !important + color: #5A2A82 !important + margin: 0 0 4px 0 !important + line-height: 1.2 !important + @media (min-width: 640px) + font-size: 20px !important + @media (min-width: 768px) + font-size: 24px !important + // Dark mode support + html.dark & + color: white !important + +.whats_new_subtitle + font-size: 12px !important + color: #5A2A82 !important + line-height: 1.5 !important + margin: 0 !important + @media (min-width: 640px) + font-size: 14px !important + // Dark mode support + html.dark & + color: #9ca3af !important + +.featured_image_section + padding: 0 16px 16px !important + display: block + @media (min-width: 640px) + padding: 0 24px 24px !important + +.featured_image_link + display: block + position: relative + border-radius: 12px + overflow: hidden + transition: transform 0.3s + &:hover + transform: scale(1.02) + .featured_image + transform: scale(1.05) + +.featured_image + width: 100% + height: auto + display: block + max-width: 100% + transition: transform 0.3s + +// Join Now Card - Figma Design (p-6 wrapper) +.join_now_card + background: white + border-radius: 16px + overflow: hidden + height: 100% + padding: 24px + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) + transition: box-shadow 0.2s + &:hover + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23) + html.dark & + background: #2D1548 + box-shadow: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) + &:hover + box-shadow: 0 3px 6px rgba(0,0,0,0.32), 0 3px 6px rgba(0,0,0,0.46) + + +.join_header + display: flex + align-items: center + gap: 12px !important + margin-bottom: 16px !important + padding: 0 !important + +.join_icon + width: 40px + height: 40px + background: linear-gradient(135deg, #1D0631 0%, #5A2A82 100%) + border-radius: 12px + display: flex + align-items: center + justify-content: center + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) + flex-shrink: 0 + @media (min-width: 640px) + width: 48px + height: 48px + i + font-size: 20px + color: white + @media (min-width: 640px) + font-size: 24px +.join_header_text + flex: 1 + +.join_title + font-size: 18px !important + font-weight: 700 !important + color: #5A2A82 !important + margin: 0 !important + line-height: 1.2 !important + @media (min-width: 640px) + font-size: 20px !important + @media (min-width: 768px) + font-size: 24px !important + // Dark mode support + html.dark & + color: white !important + +.join_subtitle + font-size: 12px !important + color: #6b7280 !important + line-height: 1.5 !important + margin: 0 !important + @media (min-width: 640px) + font-size: 14px !important + // Dark mode support + html.dark & + color: #9ca3af !important + +.benefits_list + padding: 0 !important + margin-bottom: 24px !important + // Figma: space-y-3 (12px gap) + > * + * + margin-top: 12px !important + +.benefit_item + display: flex + align-items: flex-start + gap: 12px + +.benefit_icon + width: 28px + height: 28px + border-radius: 8px + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + margin-top: 2px + @media (min-width: 640px) + width: 32px + height: 32px + +.benefit_icon_award + background: rgba(90, 42, 130, 0.1) + i + font-size: 14px + color: #5A2A82 + @media (min-width: 640px) + font-size: 16px + // Dark mode support + html.dark & + background: rgba(90, 42, 130, 0.2) + i + color: #5A2A82 + +.benefit_icon_trending + background: rgba(29, 6, 49, 0.1) + i + font-size: 14px + color: #1D0631 + @media (min-width: 640px) + font-size: 16px + // Dark mode support + html.dark & + background: rgba(90, 42, 130, 0.2) + i + color: #1D0631 + +.benefit_content + flex: 1 + +.benefit_title + font-size: 12px !important + font-weight: 600 !important + color: #111827 !important + margin: 0 0 4px 0 !important + line-height: 1.2 !important + @media (min-width: 640px) + font-size: 14px !important + // Dark mode support + html.dark & + color: white !important + +.benefit_description + font-size: 10px !important + color: #6b7280 !important + line-height: 1.625 !important + margin: 0 !important + @media (min-width: 640px) + font-size: 12px !important + // Dark mode support + html.dark & + color: #9ca3af !important + +.join_cta + padding: 0 !important + +.btn_join_cta + width: 100% + padding: 12px 24px !important + background: linear-gradient(90deg, #1D0631 0%, #5A2A82 100%) !important + color: white !important + border-radius: 8px !important + font-weight: 600 !important + transition: all 0.3s + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) + display: flex + align-items: center + justify-content: center + gap: 8px + font-size: 14px !important + text-decoration: none !important + border: none + cursor: pointer + @media (min-width: 640px) + font-size: 16px !important + &:hover + background: linear-gradient(90deg, rgba(29, 6, 49, 0.9) 0%, rgba(90, 42, 130, 0.9) 100%) !important + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important + color: white !important + text-decoration: none !important + i + font-size: 14px + @media (min-width: 640px) + font-size: 16px + +// User Journeys Section +.user_journeys_section + background: #1D0631 + padding: 32px 0 !important + width: 100vw + margin-left: calc(-50vw + 50%) + margin-right: calc(-50vw + 50%) + @media (max-width: 768px) + padding: 16px 0 !important + + // Matches Figma: max-w-7xl mx-auto + .container + max-width: 1280px + margin: 0 auto + padding: 0 16px + @media (min-width: 640px) + padding: 0 24px + @media (min-width: 1024px) + padding: 0 32px + +.journeys_header + text-align: center + margin-bottom: 48px + +// Matches Figma: text-2xl sm:text-3xl +.journeys_title + font-size: 24px + font-weight: 700 !important + color: white + margin-bottom: 12px + @media (min-width: 640px) + font-size: 30px + +// Matches Figma: text-base sm:text-lg max-w-2xl +.journeys_subtitle + font-size: 16px + color: #d1d5db + max-width: 672px + margin: 0 auto + @media (min-width: 640px) + font-size: 18px + +// Mobile View - Collapsible +// Matches Figma: lg:hidden (shows below 768px, then desktop grid takes over) +.journeys_mobile + display: block + @media (min-width: 768px) + display: none + +.journey_card + position: relative + border-radius: 12px + overflow: hidden + border: 0.8px solid #ffb91a + background: #1D0631 + margin-bottom: 16px + transition: all 0.3s + +// Matches Figma: rounded-xl p-4 +.journey_card_header + width: 100% + display: flex + align-items: center + gap: 16px + padding: 16px + text-align: left + transition: background 0.3s + background: #1D0631 + border: none + cursor: pointer + border-radius: 12px + &:hover + background: rgba(90, 42, 130, 0.2) + +.journey_header_icon + width: 40px + height: 40px + border-radius: 8px + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) + background: linear-gradient(135deg, #5A2A82 0%, #ffb91a 100%) + i + font-size: 20px + color: white + +.journey_header_title + flex: 1 + font-size: 14px + font-weight: 600 !important + color: white + overflow-wrap: break-word + +.journey_toggle_icon + flex-shrink: 0 + i + font-size: 20px + color: white + +// Matches Figma: px-4 pb-4 +.journey_card_content + display: none + padding: 0 16px 16px + background: #1D0631 + border-radius: 0 0 12px 12px + +.journey_card.expanded .journey_card_content + display: block + animation: slideDown 0.3s ease-out + +@keyframes slideDown + from + opacity: 0 + transform: translateY(-10px) + to + opacity: 1 + transform: translateY(0) + +.journey_description + font-size: 14px + color: #d1d5db + line-height: 1.5 + margin-bottom: 16px + overflow-wrap: break-word + +.journey_steps + background: rgba(90, 42, 130, 0.2) + border-radius: 8px + padding: 16px + border: 1px solid rgba(90, 42, 130, 0.4) + +.steps_title + font-size: 12px + font-weight: 600 + color: #e5e7eb + text-transform: uppercase + letter-spacing: 0.05em + margin-bottom: 8px + +.steps_list + list-style: none + padding: 0 + margin: 0 + li + display: flex + align-items: flex-start + gap: 8px + font-size: 14px + color: #d1d5db + margin-bottom: 6px + &:last-child + margin-bottom: 0 + +.step_bullet + color: #ffb91a + font-weight: 700 + +// Desktop View - Grid +// Matches Figma: hidden lg:grid md:grid-cols-2 lg:grid-cols-3 gap-6 +.journeys_desktop + display: none + @media (min-width: 768px) + display: grid + grid-template-columns: repeat(2, 1fr) + gap: 24px + min-width: 0 + @media (min-width: 1024px) + grid-template-columns: repeat(3, 1fr) + +// Matches Figma: rounded-xl p-6 +.journey_grid_card + border-radius: 12px + padding: 24px + border: 1px solid rgba(90, 42, 130, 0.4) + background: #1D0631 + transition: all 0.3s + min-width: 0 + &:hover + box-shadow: 0 10px 15px rgba(0, 0, 0, 0.3) + border-color: #ffb91a + +.grid_card_icon + width: 56px + height: 56px + border-radius: 12px + display: flex + align-items: center + justify-content: center + margin-bottom: 16px + transition: transform 0.3s + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) + background: linear-gradient(135deg, #5A2A82 0%, #ffb91a 100%) + i + font-size: 28px + color: white + +.journey_grid_card:hover .grid_card_icon + transform: scale(1.1) + +.grid_card_title + font-size: 17px + font-weight: 700 !important + color: white + margin-bottom: 8px + line-height: 1.2 + overflow-wrap: break-word + +.grid_card_description + font-size: 14px + color: #d1d5db + margin-bottom: 16px + line-height: 1.5 + overflow-wrap: break-word + +.grid_card_steps + background: rgba(90, 42, 130, 0.2) + border-radius: 8px + padding: 16px + border: 1px solid rgba(90, 42, 130, 0.4) + +// Project Swimlanes Section - Matches Figma: py-8 px-4 sm:px-6 lg:px-8 bg-gray-50 +.project_swimlanes_section + @extend .section_bg + padding: 8px 16px !important + @media (min-width: 640px) + padding: 8px 24px !important + @media (min-width: 1024px) + padding: 8px 32px !important + html.dark & + background: #1D0631 + +// Matches Figma: max-w-7xl mx-auto +.swimlanes_container + max-width: 1280px + margin: 0 auto + +// Matches Figma: grid lg:grid-cols-3 md:grid-cols-2 gap-6 +.swimlanes_grid + display: grid + grid-template-columns: 1fr + gap: 24px + @media (min-width: 768px) + grid-template-columns: repeat(2, 1fr) + @media (min-width: 1024px) + grid-template-columns: repeat(3, 1fr) + +.swimlane_column + display: flex + flex-direction: column + +// Matches Figma: mb-4 +.swimlane_header + margin-bottom: 16px + +// Matches Figma: text-base sm:text-lg md:text-xl font-bold +.swimlane_title + font-size: 16px + font-weight: 700 !important + color: #111827 + transition: color 0.3s ease + @media (min-width: 640px) + font-size: 18px + @media (min-width: 768px) + font-size: 20px + html.dark & + color: #ffffff + +// Matches Figma: space-y-3 +.swimlane_content + display: flex + flex-direction: column + gap: 12px + +// Compact Project Card Styles +.compact_project_card + position: relative + border-radius: 16px + overflow: hidden + transition: all 0.3s ease + cursor: pointer + border: 1px solid rgba(229, 231, 235, 0.8) + background: white + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) + height: 100% + display: flex + flex-direction: column + + &:hover + border-color: #5A2A82 + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23) + + html.dark & + background: #2D1548 + border-color: rgba(255, 255, 255, 0.1) + box-shadow: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) + &:hover + border-color: #ffb91a + box-shadow: 0 3px 6px rgba(0,0,0,0.32), 0 3px 6px rgba(0,0,0,0.46) + +.compact_project_card .compact_card_inner + padding: 12px + transition: all 0.3s ease + height: 100% + display: flex + flex-direction: column + @media (min-width: 640px) + padding: 16px + +.compact_project_card .compact_card_header + display: flex + align-items: flex-start + gap: 8px + margin-bottom: 8px + @media (min-width: 640px) + gap: 12px + +.compact_project_card .project_logo + flex-shrink: 0 + width: 32px + height: 32px + @media (min-width: 640px) + width: 40px + height: 40px + img, p + width: 100% !important + height: 100% !important + border-radius: 8px !important + object-fit: cover + border: 1px solid #e5e7eb !important + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important + html.dark & + border-color: #4b5563 !important + p + display: flex !important + align-items: center !important + justify-content: center !important + margin: 0 !important + font-size: 16px !important + font-weight: 600 !important + +.compact_project_card .project-icon-placeholder + width: 100% + height: 100% + display: flex + align-items: center + justify-content: center + background: #f3f4f6 + border-radius: 8px + font-size: 16px + font-weight: 600 + color: #4b5563 + border: 1px solid #e5e7eb + html.dark & + background: rgba(255, 255, 255, 0.1) + border-color: #4b5563 + color: #9ca3af + +.compact_project_card .account-avatar + width: 100% + height: 100% + border-radius: 8px + object-fit: cover + border: 1px solid #e5e7eb + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) + html.dark & + border-color: #4b5563 + +.compact_project_card .project_info + flex: 1 + min-width: 0 + +.compact_project_card .project_name_row + display: flex + align-items: center + gap: 8px + +.compact_project_card .project_name + color: #111827 + font-size: 14px + font-weight: 600 + text-decoration: none + transition: color 0.3s ease + display: inline-block + line-height: 1.4 + @media (min-width: 640px) + font-size: 16px + &:hover + color: #5A2A82 + html.dark & + color: #ffffff !important + &:hover + color: #ffb91a !important + span + display: inline + +.compact_project_card .trending_icon + flex-shrink: 0 + i + color: #ff6b35 + font-size: 12px + @media (min-width: 640px) + font-size: 14px + +.compact_project_card .compact_card_description + margin-bottom: 12px + height: 42px + display: flex + align-items: center + @media (min-width: 640px) + height: 44px + p + font-size: 12px + color: #6b7280 + line-height: 1.5 + margin: 0 + overflow: hidden + text-overflow: ellipsis + display: -webkit-box + -webkit-line-clamp: 2 + -webkit-box-orient: vertical + @media (min-width: 640px) + font-size: 14px + html.dark & + color: #9ca3af + +.compact_project_card .compact_card_footer + display: flex + align-items: center + justify-content: space-between + font-size: 10px + @media (min-width: 640px) + font-size: 12px + +.compact_project_card .footer_left + display: flex + align-items: center + gap: 12px + +.compact_project_card .contributor_info + display: flex + align-items: center + gap: 4px + color: #6b7280 + html.dark & + color: #9ca3af + i + font-size: 10px + @media (min-width: 640px) + font-size: 12px + span + font-size: 10px + @media (min-width: 640px) + font-size: 12px + +.compact_project_card .footer_right + display: flex + align-items: center + +.compact_project_card .language_badge + display: inline-flex + align-items: center + justify-content: center + padding: 2px 8px + border-radius: 9999px + font-size: 10px + font-weight: 500 + line-height: 1.5 + white-space: nowrap + text-align: center + min-width: 40px + background: rgba(255, 185, 26, 0.3) + color: #1D0631 + transition: all 0.3s ease + @media (min-width: 640px) + font-size: 12px + html.dark & + background: #ffb91a + color: #000000 + +// Responsive visibility helpers +.desktop_only + display: none !important + @media (min-width: 768px) + display: block !important + +.mobile_only + display: block + @media (min-width: 768px) + display: none !important + +.mobile_card + width: 100% + +.hidden_card + width: 100% + +// Show More Button +.show_more_btn + width: 100% + font-size: 14px + background: white + color: #5A2A82 + border: 2px solid #5A2A82 + padding: 12px 16px + border-radius: 12px + font-weight: 600 + cursor: pointer + transition: all 0.3s ease + margin-top: 8px + display: block + &:hover + background: #5A2A82 + color: white + html.dark & + background: transparent + color: #ffb91a + border-color: #ffb91a + &:hover + background: #ffb91a + color: #1D0631 + +// Pagination Controls +.pagination_controls + display: flex + align-items: center + justify-content: center + gap: 16px + margin-top: 16px + padding: 12px + +.pagination_btn + display: flex + align-items: center + justify-content: center + width: 32px + height: 32px + background: white + border: 1px solid #e5e7eb + border-radius: 8px + cursor: pointer + transition: all 0.3s ease + color: #5A2A82 + &:hover:not([disabled]) + background: #5A2A82 + color: white + border-color: #5A2A82 + &[disabled] + opacity: 0.4 + cursor: not-allowed + i + font-size: 14px + html.dark & + background: rgba(255, 255, 255, 0.1) + border-color: #4b5563 + color: white + &:hover:not([disabled]) + background: #5A2A82 + border-color: #5A2A82 + +.pagination_info + font-size: 14px + color: #6b7280 + font-weight: 500 + html.dark & + color: #9ca3af + .current_page + color: #5A2A82 + font-weight: 700 + html.dark & + color: #ffb91a + +.swimlane_project_item + display: none + &[data-page="1"] + display: block + +// View All Projects Button - Matches Figma: pt-4 pb-8 +.view_all_container + padding-top: 32px + padding-bottom: 32px + padding-left: 16px + padding-right: 16px + display: flex + justify-content: center + background: #f9fafb + transition: background 0.3s ease + @media (min-width: 640px) + padding-left: 24px + padding-right: 24px + @media (min-width: 1024px) + padding-left: 32px + padding-right: 32px + html.dark & + background: #1D0631 + +// Matches Figma: flex items-center gap-2 px-6 py-3 bg-[#ffb91a] rounded-lg +.btn_view_all_projects + display: inline-flex + align-items: center + gap: 8px + padding: 12px 24px + background: #ffb91a + color: #1D0631 + border-radius: 12px + font-weight: 600 + font-size: 14px + text-decoration: none + transition: all 0.3s ease + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) + @media (min-width: 640px) + font-size: 16px + &:hover + background: rgba(255, 185, 26, 0.9) + box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1) + color: #1D0631 + html.dark & + color: #1D0631 !important + &:hover + color: #1D0631 !important + i + font-size: 16px diff --git a/app/assets/stylesheets/home_search.sass b/app/assets/stylesheets/home_search.sass new file mode 100644 index 000000000..b873b26f5 --- /dev/null +++ b/app/assets/stylesheets/home_search.sass @@ -0,0 +1,145 @@ +// ======================================== +// Home Page Search Bar - Figma Design +// ======================================== +// Dedicated styles for search input with higher specificity +// to override Bootstrap and other base styles + +// Hero Section Search Container - Nested under .hero_section +.hero_section + .search_form#search_form + margin-bottom: 0 + position: relative + width: 100% + + .search_input_wrapper + position: relative + width: 100% + display: block + + // Search Icon Wrapper - Figma Design +.search_icon_wrapper + position: absolute + top: 0 + bottom: 0 + left: 8px + display: flex + align-items: center + pointer-events: none + @media (min-width: 640px) + left: 24px + +// Search Icon - Figma specs +.search_icon + width: 14px + height: 14px + font-size: 14px + line-height: 0.5 + color: #9ca3af + transition: color 0.3s ease + @media (min-width: 640px) + width: 20px + height: 20px + font-size: 20px + // Dark mode support + html.dark & + color: #6b7280 + + + // Search Input - Figma: pl-8 pr-3 py-2.5, sm:pl-14 sm:pr-4 sm:py-4 + input.search_input + width: 100% + padding: 10px 12px 10px 32px + font-size: 11px + font-weight: 400 + line-height: 1.5 + height: auto + border: 2px solid #e5e7eb + border-radius: 16px + outline: none + background: #ffffff + background-clip: padding-box + color: #111827 + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) + transition: all 0.3s ease + appearance: none + -webkit-appearance: none + -moz-appearance: none + vertical-align: middle + box-sizing: border-box + + // Placeholder styling - Figma specs + &::placeholder + color: #9ca3af + opacity: 1 + + // Hover state - Figma specs + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) + border-color: #d1d5db + + // Focus state - Figma specs + &:focus + border-color: #0E4B7A + outline: none + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) + background: #ffffff + &::placeholder + color: #d1d5db + + // Desktop - Figma: sm:pl-14 sm:pr-4 sm:py-4 + @media (min-width: 640px) + padding: 16px 16px 16px 56px + font-size: 16px + border-radius: 16px + + // Dark mode support + html.dark & + background: #1D0631 + color: #ffffff + border-color: #4b5563 + &::placeholder + color: #6b7280 + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.1) + border-color: #6b7280 + &:focus + border-color: #2E8B9E + background: #1D0631 + box-shadow: 0 0 0 4px rgba(46, 139, 158, 0.1) + +// ======================================== +// Bootstrap Override - Global Search Form +// ======================================== +// High specificity overrides for Bootstrap and other frameworks +// Ensures Figma padding is applied: pl-8 pr-3 py-2.5, sm:pl-14 sm:pr-4 sm:py-4 +form#search_form + input[type="text"].search_input, + input.search_input + width: 100% !important + padding: 10px 12px 10px 32px !important + font-size: 11px !important + height: auto !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + box-sizing: border-box !important + @media (min-width: 640px) + padding: 16px 16px 16px 56px !important + font-size: 16px !important + &:focus + border-color: #5A2A82 !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + outline: none !important + // Dark mode support + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &:focus + border-color: #ffb91a !important + background: #1D0631 !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important diff --git a/app/assets/stylesheets/kudo_mantle.sass b/app/assets/stylesheets/kudo_mantle.sass index bbf89c8e0..a1a07ed2e 100644 --- a/app/assets/stylesheets/kudo_mantle.sass +++ b/app/assets/stylesheets/kudo_mantle.sass @@ -1,20 +1,498 @@ table#kudo_mantle - :margin-left 30% - :margin-right auto + margin-left: 30% + margin-right: auto td .arrow_up, .arrow_down - :background asset_url('kudos/arrows.png') 0 0 no-repeat - :width 111px - :position absolute + background: asset-url('kudos/arrows.png') 0 0 no-repeat + width: 111px + position: absolute .arrow_up - :margin 15px 0px 0px -5px - :height 42px - :background-position 0px 0px + margin: 15px 0 0 -5px + height: 42px + background-position: 0 0 .arrow_down - :height 36px - :background-position 0px -42px - :margin 23px 0px 0px 3px + height: 36px + background-position: 0 -42px + margin: 23px 0 0 3px td.rank - :font-size 10px - :text-align center - :font-family arial,sans-serif + font-size: 10px + text-align: center + font-family: arial, sans-serif + +@media (max-width: 767px) + table#kudo_mantle + margin-left: auto !important + margin-right: auto !important + width: 100% !important + max-width: 340px + + td[width] + width: 80px !important + padding: 0 !important + + td + position: relative + + .arrow_up, .arrow_down + width: 80px !important + background-size: 80px auto !important + + .arrow_up + margin: 10px 0 0 0 !important + + .arrow_down + margin: 16px 0 0 0 !important + +// ── Give / Undo Kudo buttons ──────────────────────────────────────────────── +.btn.kudo-btn + display: inline-flex + align-items: center + gap: 5px + +.kudo-btn-icon + width: 14px + height: 14px + stroke: currentColor + vertical-align: middle + flex-shrink: 0 + +// ── Undo/rescind inline button (kudos list) ───────────────────────────────── +a:has(i.rescind-kudos) + display: inline-flex + align-items: center + gap: 4px + padding: 2px 8px + border-radius: 9999px + border: 1px solid rgba(90, 42, 130, 0.4) + background: transparent + color: #5A2A82 + font-size: 11px + font-weight: 500 + text-decoration: none + transition: background 0.15s, border-color 0.15s + &:hover + background: rgba(90, 42, 130, 0.08) + border-color: #5A2A82 + text-decoration: none + html.dark & + border-color: rgba(192, 132, 252, 0.4) + color: #c084fc + &:hover + background: rgba(192, 132, 252, 0.1) + border-color: #c084fc + +// ── Kudo Modal: ThickBox container overrides ──────────────────────────────── +// .kudo-active is added via JS (fallback for browsers without :has() support) +#TB_window.kudo-active, +#TB_window:has(.kudo-form) + border: none !important + border-radius: 24px !important + overflow: hidden !important + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.35) !important + width: 520px !important + left: 50% !important + top: 50% !important + margin: 0 !important + transform: translate(-50%, -50%) !important + +#TB_window.kudo-active #TB_title, +#TB_window:has(.kudo-form) #TB_title + height: 8px !important + background: linear-gradient(to right, #5A2A82, #9333ea, #ffb91a) !important + overflow: visible + position: relative + +#TB_window.kudo-active #TB_ajaxWindowTitle, +#TB_window:has(.kudo-form) #TB_ajaxWindowTitle + display: none !important + +#TB_window.kudo-active #TB_closeAjaxWindow, +#TB_window:has(.kudo-form) #TB_closeAjaxWindow + float: none !important + position: absolute !important + top: 20px !important + right: 16px !important + padding: 0 !important + margin: 0 !important + height: auto !important + z-index: 10 + a + color: #9ca3af !important + font-size: 12px + text-decoration: none + transition: color 0.15s + a:hover + color: #4b5563 !important + +#TB_window.kudo-active #TB_ajaxContent, +#TB_window:has(.kudo-form) #TB_ajaxContent + height: auto !important + overflow: hidden !important + padding: 0 !important + width: 100% !important + background: #ffffff + +// ── Kudo form inner styles ─────────────────────────────────────────────────── +.kudo-form + background: #ffffff + font-family: inherit + + .kudo-modal-header + display: flex + align-items: flex-start + gap: 12px + padding: 20px 80px 16px 24px + border-bottom: 1px solid #e5e7eb + + .kudo-modal-icon-wrap + width: 48px + height: 48px + min-width: 48px + border-radius: 16px + background: linear-gradient(135deg, #5A2A82, #9333ea) + display: flex + align-items: center + justify-content: center + box-shadow: 0 4px 12px rgba(90, 42, 130, 0.3) + svg + width: 24px + height: 24px + stroke: #ffffff + + .kudo-modal-header-text + h2 + font-size: 17px + font-weight: 700 + color: #111827 + margin: 0 0 4px 0 + line-height: 1.3 + p + font-size: 12px + color: #6b7280 + margin: 0 + + .kudo-modal-body + padding: 20px 24px + + .kudo-modal-card + background: linear-gradient(135deg, #faf5ff, #fffbeb) + border: 1px solid rgba(167, 139, 250, 0.3) + border-radius: 16px + padding: 20px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) + + .kudo-message-label + display: block + > span:first-child + display: block + font-size: 13px + font-weight: 600 + color: #111827 + margin-bottom: 10px + + .kudo-textarea + width: 100% + box-sizing: border-box + padding: 12px 16px + border: 2px solid #e5e7eb + border-radius: 12px + background: #ffffff + color: #111827 + font-size: 13px + resize: none + outline: none + transition: border-color 0.2s + line-height: 1.5 + font-family: inherit + display: block + margin-top: 0 + &:focus + border-color: #5A2A82 !important + outline: none !important + box-shadow: none !important + + .kudo-char-counter + display: flex + justify-content: space-between + align-items: center + margin-top: 8px + > span:first-child + font-size: 11px + color: #9ca3af + + .kudo-count + font-size: 11px + font-weight: 500 + color: #5A2A82 + + .kudo-modal-footer + padding: 0 24px 24px + display: flex + gap: 12px + align-items: center + + .kudo-btn-cancel + flex: 1 + background: transparent + border: 2px solid #e5e7eb + color: #374151 + padding: 12px 20px + border-radius: 12px + font-size: 13px + font-weight: 600 + cursor: pointer + transition: background-color 0.2s, border-color 0.2s + line-height: 1 + &:hover + background-color: #f9fafb + border-color: #d1d5db + + .kudo-btn-submit + flex: 1 + display: flex + align-items: center + justify-content: center + gap: 8px + background: linear-gradient(to right, #5A2A82, #9333ea) + border: none + color: #ffffff + padding: 12px 20px + border-radius: 12px + font-size: 13px + font-weight: 700 + cursor: pointer + box-shadow: 0 10px 15px -3px rgba(168, 85, 247, 0.3) + transition: background 0.2s, box-shadow 0.2s + line-height: 1 + &:hover + background: linear-gradient(to right, #4a1f6e, #7c3aed) + box-shadow: 0 10px 15px -3px rgba(168, 85, 247, 0.4) + + .kudo-submit-icon + width: 16px + height: 16px + stroke: #ffffff + +// ── Dark mode ──────────────────────────────────────────────────────────────── +html.dark #TB_window:has(.kudo-form) + #TB_ajaxContent + background: #1D0631 + #TB_closeAjaxWindow + a + color: #6b7280 !important + a:hover + color: #d1d5db !important + +html.dark .kudo-form + background: #1D0631 + + .kudo-modal-header + border-bottom-color: rgba(90, 42, 130, 0.2) + + .kudo-modal-header-text + h2 + color: #ffffff + p + color: #9ca3af + + .kudo-modal-card + background: linear-gradient(135deg, #2D1548, #2D1548) + border-color: rgba(90, 42, 130, 0.3) + + .kudo-message-label + > span:first-child + color: #ffffff + + .kudo-textarea + background: rgba(29, 6, 49, 0.6) + border-color: rgba(90, 42, 130, 0.3) + color: #ffffff + &::placeholder + color: #6b7280 + &:focus + border-color: #ffb91a !important + + .kudo-count + color: #ffb91a + + .kudo-btn-cancel + border-color: rgba(90, 42, 130, 0.3) + color: #d1d5db + &:hover + background-color: rgba(45, 21, 72, 0.5) + border-color: rgba(90, 42, 130, 0.5) + + .kudo-btn-submit + background: linear-gradient(to right, #5A2A82, #9333ea) + &:hover + background: linear-gradient(to right, #4a1f6e, #7c3aed) + +// ── Kudo loader: tint GIF to purple (#5A2A82) light / yellow (#ffb91a) dark ── +#TB_load + img + filter: invert(18%) sepia(54%) saturate(1447%) hue-rotate(254deg) brightness(91%) !important + +html.dark #TB_load + img + filter: invert(69%) sepia(72%) saturate(653%) hue-rotate(1deg) brightness(105%) !important + +// ── Mobile responsive ──────────────────────────────────────────────────────── +@media (max-width: 540px) + #TB_window.kudo-active, + #TB_window:has(.kudo-form) + width: calc(100vw - 24px) !important + border-radius: 16px !important + left: 50% !important + top: 50% !important + + .kudo-form + .kudo-modal-header + padding: 16px 48px 14px 16px !important + gap: 10px + + .kudo-modal-icon-wrap + width: 40px !important + height: 40px !important + min-width: 40px !important + border-radius: 12px !important + svg + width: 20px !important + height: 20px !important + + .kudo-modal-header-text + h2 + font-size: 15px !important + p + font-size: 11px !important + + .kudo-modal-body + padding: 16px !important + + .kudo-modal-card + padding: 14px !important + + .kudo-modal-footer + padding: 0 16px 16px !important + gap: 8px !important + + .kudo-btn-cancel, + .kudo-btn-submit + padding: 10px 14px !important + font-size: 12px !important + +// ── Kudos index: two-column card layout ───────────────────────────────────── +.kudos-grid + display: grid + grid-template-columns: 1fr + gap: 24px + margin-bottom: 24px + @media (min-width: 768px) + grid-template-columns: 1fr 1fr + +.kudos-card + &__header + display: flex + align-items: center + gap: 10px + padding: 16px 20px + border-bottom: 1px solid #e5e7eb + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + &__icon + width: 36px + height: 36px + border-radius: 10px + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + svg + width: 18px + height: 18px + &--received + background: rgba(16, 185, 129, 0.1) + svg + stroke: #10b981 + &--given + background: rgba(90, 42, 130, 0.1) + svg + stroke: #5A2A82 + html.dark & + background: rgba(255, 185, 26, 0.1) + svg + stroke: #ffb91a + &__title + font-size: 16px + font-weight: 700 + color: #111827 + margin: 0 + html.dark & + color: #ffffff + &__list + > .kudos-item + .kudos-item + border-top: 1px solid #f3f4f6 + html.dark & + border-top-color: rgba(90, 42, 130, 0.2) + &__empty + padding: 16px 20px + color: #6b7280 + font-size: 13px + margin: 0 + html.dark & + color: #9ca3af + +.kudos-item + display: flex + align-items: flex-start + gap: 12px + padding: 12px 20px + transition: background-color 0.15s + &:hover + background-color: #f9fafb + html.dark & + background-color: rgba(29, 6, 49, 0.5) + &__avatar + flex-shrink: 0 + margin-top: 2px + &__body + flex: 1 + min-width: 0 + &__name-row + display: flex + align-items: center + gap: 8px + flex-wrap: wrap + &__name + font-size: 14px + font-weight: 500 + color: #111827 + html.dark & + color: #ffffff + &__new + font-size: 11px + color: #047857 + font-weight: 600 + html.dark & + color: #6ee7b7 + &__project + font-size: 12px + color: #6b7280 + margin-top: 2px + html.dark & + color: #9ca3af + &__message + font-size: 12px + color: #6b7280 + margin-top: 4px + font-style: italic + html.dark & + color: #9ca3af + &__aka + font-size: 12px + color: #6b7280 + margin-top: 6px + padding-top: 6px + border-top: 1px dashed #e5e7eb + html.dark & + color: #9ca3af + border-top-color: rgba(90, 42, 130, 0.2) diff --git a/app/assets/stylesheets/languages.sass b/app/assets/stylesheets/languages.sass new file mode 100644 index 000000000..790769204 --- /dev/null +++ b/app/assets/stylesheets/languages.sass @@ -0,0 +1,834 @@ +// Languages Compare Page - Modern Card-Based Design + +// Colors (matching design system) +$purple-primary: #5A2A82 +$purple-dark: #1D0631 +$purple-card-dark: #2D1548 +$yellow-accent: #ffb91a +$gray-50: #f9fafb +$gray-100: #f3f4f6 +$gray-200: #e5e7eb +$gray-400: #9ca3af +$gray-600: #4b5563 +$gray-700: #374151 +$gray-900: #111827 + +// Page Container +.languages-compare-container + max-width: 1280px + margin: 0 auto + padding: 24px 16px + + @media (max-width: 768px) + padding: 16px 12px + +// Languages Index Page Container +.languages-index-container + max-width: 1280px + margin: 0 auto + padding: 24px 16px + + @media (max-width: 768px) + padding: 16px 0 + +// Languages Show Page Container +.languages-show-container + max-width: 1280px + margin: 0 auto + padding: 24px 16px + + @media (max-width: 768px) + padding: 16px 0 + +.languages-index-title + font-size: 36px + font-weight: bold + color: $purple-primary + margin: 0 0 24px 0 + + html.dark & + color: white + + @media (max-width: 640px) + font-size: 30px + +// Header Section +.languages-header + margin-bottom: 24px + +.languages-title + font-size: 36px + font-weight: bold + color: $purple-primary + margin: 0 0 8px 0 + + html.dark & + color: white + + @media (max-width: 640px) + font-size: 30px + +// Measure Buttons Card +.languages-measures-card + background-color: white + border-radius: 12px + border: 1px solid $gray-200 + padding: 16px + margin-bottom: 24px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + + html.dark & + background-color: $purple-card-dark + border-color: rgba(255, 255, 255, 0.1) + +.measure-buttons-group + display: flex + gap: 8px + flex-wrap: wrap + +.measure-btn + padding: 8px 16px + background-color: white + color: $gray-700 + border: 1px solid $gray-200 + border-radius: 8px + font-size: 13px + font-weight: 500 + text-decoration: none + transition: all 0.2s ease + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) + cursor: pointer + + &:hover + border-color: $purple-primary + color: $purple-primary + transform: translateY(-1px) + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) + + &.active + background-color: $purple-primary + color: white + border-color: $purple-primary + box-shadow: 0 2px 4px rgba(90, 42, 130, 0.3) + background-color: #5A2A82 !important + + html.dark & + background-color: $purple-dark + color: $gray-200 + border-color: rgba(255, 255, 255, 0.1) + + &:hover + border-color: $yellow-accent + color: $yellow-accent + + &.active + background-color: $yellow-accent !important + color: $purple-dark !important + border-color: $yellow-accent !important + + @media (max-width: 640px) + font-size: 12px + padding: 6px 12px + +// Info Card +.languages-info-card + background-color: white + border-radius: 12px + border: 1px solid $gray-200 + padding: 20px + margin-bottom: 24px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + + html.dark & + background-color: $purple-card-dark + border-color: rgba(255, 255, 255, 0.1) + +.languages-compare-container + .section-heading + font-size: 16px !important + font-weight: 600 + color: $gray-900 + margin: 0 0 12px 0 + + html.dark & + color: white + + .info-text + font-size: 12px + color: $gray-600 + line-height: 1.6 + margin: 0 + + html.dark & + color: $gray-400 + + .info-link + color: $purple-primary + text-decoration: none + font-weight: 500 + margin-left: 4px + transition: color 0.2s ease + + &:hover + text-decoration: underline + + html.dark & + color: $yellow-accent + +// Content Card +.languages-content-card + background-color: white + border-radius: 12px + border: 1px solid $gray-200 + padding: 24px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + + html.dark & + background-color: $purple-card-dark + border-color: rgba(255, 255, 255, 0.1) + +.content-grid + display: grid + grid-template-columns: 1fr 280px + gap: 24px + align-items: start + + @media (max-width: 1024px) + grid-template-columns: 1fr + gap: 20px + +// Chart Section +.chart-section + min-height: 300px + +.chart-wrapper, +.chart.watermark440 + background-color: $gray-50 + border-radius: 8px + border: 1px solid $gray-200 + padding: 16px + height: 300px + display: flex + align-items: center + justify-content: center + + html.dark & + background-color: rgba(0, 0, 0, 0.2) + border-color: rgba(255, 255, 255, 0.05) + +// Selector Section +.selector-section + display: flex + flex-direction: column + gap: 16px + +.language-selectors + display: flex + flex-direction: column + gap: 12px + +.language-selector-item + display: flex + align-items: center + gap: 12px + +.lang-color-dot + width: 16px + height: 16px + border-radius: 50% + flex-shrink: 0 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) + + &.empty + background-color: $gray-200 + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1) + + html.dark & + background-color: rgba(255, 255, 255, 0.1) + +.select-wrapper + flex: 1 + +// Language Dropdown Styles +.language-dropdown-wrapper + position: relative + display: flex + +.language-dropdown-trigger + display: flex + align-items: center + gap: 8px + height: 40px + padding: 0 12px + width: 100% + background-color: white + border: 1px solid $gray-200 + border-radius: 8px + color: $gray-900 + font-size: 14px + font-weight: 400 + white-space: nowrap + transition: all 0.2s ease + cursor: pointer + box-sizing: border-box + line-height: normal + + &:hover + border-color: $purple-primary + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) + + &:focus + outline: none + border-color: $purple-primary + box-shadow: 0 0 0 3px rgba(90, 42, 130, 0.1) + + html.dark & + background-color: rgba(255, 255, 255, 0.05) + border-color: rgba(255, 255, 255, 0.1) + color: white + + &:hover + border-color: $yellow-accent + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) + + &:focus + border-color: $yellow-accent + box-shadow: 0 0 0 3px rgba(255, 185, 26, 0.1) + + i + font-size: 12px + transition: transform 0.2s ease + + .selection + display: block + flex: 1 + text-align: left + overflow: hidden + text-overflow: ellipsis + +.language-dropdown-menu + position: absolute + top: 100% + left: 0 + right: 0 + width: 100% + margin-top: 4px + padding: 0 + background-color: white !important + border: 1px solid $gray-200 + border-radius: 8px + overflow: hidden + z-index: 1000 + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23) + max-height: 300px + overflow-y: auto + display: none + + html.dark & + background-color: #1D0631 !important + border-color: #5A2A82 + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.38), 0 6px 6px rgba(0, 0, 0, 0.46) + + &.active + display: block + + .dropdown-item + display: block + width: 100% + padding: 8px 16px + color: #111827 + font-size: 14px + font-weight: 400 + text-align: left + text-decoration: none + background: transparent + border: none + box-sizing: border-box + transition: background 0.15s ease, color 0.15s ease + cursor: pointer + + &:hover + background: #f3f4f6 + + &.active + background-color: $purple-primary !important + color: #ffffff + font-weight: 400 + + &:hover + background-color: $purple-primary + + html.dark & + color: #ffffff + + &:hover + background: #374151 + + &.active + background-color: $yellow-accent !important + color: #5A2A82 !important + font-weight: 400 + + &:hover + background-color: $yellow-accent + +.language-input + display: none + +// Update Button +.update-button-wrapper + margin-top: 8px + display: flex + justify-content: center + +.update-btn + padding: 12px 24px + font-size: 14px + font-weight: 600 + border-radius: 8px + width: auto + + @media (max-width: 768px) + width: 100% + +// Responsive adjustments - Tablet (768px and below) +@media (max-width: 768px) + .languages-compare-container + padding: 16px 12px + + .languages-index-container + padding: 16px 12px + + .languages-title + font-size: 28px + margin-bottom: 16px + + .languages-index-title + font-size: 28px + + .languages-measures-card + padding: 12px + margin-bottom: 16px + + .languages-info-card + padding: 16px + margin-bottom: 16px + + .languages-content-card + padding: 16px + + .content-grid + gap: 16px + + .languages-compare-container .section-heading + font-size: 15px + + .language-dropdown-trigger + font-size: 13px + padding: 0 10px + height: 36px + + .language-selectors + gap: 10px + + .language-selector-item + gap: 10px + + .update-btn + width: 100% + margin-left: 0 + + .measure-buttons-group + gap: 6px + + .measure-btn + font-size: 11px + padding: 6px 10px + +// Responsive adjustments - Small devices (480px and below) +@media (max-width: 480px) + .languages-compare-container + padding: 12px 8px + + .languages-index-container + padding: 12px 8px + + .languages-title + font-size: 24px + margin-bottom: 12px + + .languages-index-title + font-size: 24px + margin-bottom: 16px + + .languages-header + margin-bottom: 16px + + .languages-measures-card + padding: 8px + margin-bottom: 12px + + .measure-buttons-group + gap: 4px + + .measure-btn + font-size: 10px + padding: 4px 8px + + .languages-info-card + padding: 12px + margin-bottom: 12px + + .languages-compare-container .section-heading + font-size: 14px + margin-bottom: 10px + + .languages-compare-container .info-text + font-size: 12px + + .languages-content-card + padding: 12px + + .content-grid + gap: 12px + + .chart-section + min-height: 200px + + .chart-wrapper, + .chart.watermark440 + height: 200px + padding: 12px + + .language-selectors + gap: 8px + + .language-selector-item + gap: 8px + + .lang-color-dot + width: 14px + height: 14px + + .language-dropdown-trigger + font-size: 12px + padding: 0 8px + height: 32px + + i + font-size: 10px + + .selection + font-size: 12px + + .language-dropdown-menu + margin-top: 2px + max-height: 200px + + .dropdown-item + padding: 6px 12px + font-size: 13px + + .update-button-wrapper + margin-top: 4px + + .update-btn + padding: 10px 16px + font-size: 13px + width: 100% + +// Very small screens (under 360px) +@media (max-width: 360px) + + .languages-title + font-size: 20px + margin-bottom: 10px + + .languages-index-title + font-size: 20px + + .languages-measures-card + padding: 6px + margin-bottom: 8px + + .measure-buttons-group + gap: 2px + + .measure-btn + font-size: 10px + padding: 4px 6px + + .languages-info-card + padding: 10px + margin-bottom: 10px + + .languages-compare-container .section-heading + font-size: 13px + + .languages-compare-container .info-text + font-size: 12px + + .languages-content-card + padding: 10px + + .content-grid + gap: 10px + + .chart-section + min-height: 160px + + .chart-wrapper, + .chart.watermark440 + height: 160px + padding: 8px + + .language-selectors + gap: 6px + + .language-selector-item + gap: 6px + + .language-dropdown-trigger + font-size: 11px + padding: 0 6px + height: 28px + + i + font-size: 9px + + .language-dropdown-menu + .dropdown-item + padding: 5px 10px + font-size: 12px + + .update-btn + padding: 8px 12px + font-size: 12px + +// Language Index Page - Card Table Responsive Styles +.oh-card + background-color: white + border-radius: 12px + border: 1px solid $gray-200 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + margin-bottom: 16px + overflow: hidden + + html.dark & + background-color: $purple-card-dark + border-color: rgba(255, 255, 255, 0.1) + + h4 + margin: 0 + padding: 10px 16px + font-size: 18px + + @media (max-width: 768px) + font-size: 16px + padding: 8px 12px + + @media (max-width: 480px) + font-size: 15px + padding: 6px 10px + + // Table styling for language stats + .table + margin: 16px 0 0 0 + border-collapse: collapse + box-shadow: none !important + + @media (max-width: 768px) + margin: 12px 0 0 0 + + @media (max-width: 480px) + margin: 10px 0 0 0 + + // Desktop: horizontal layout with columns + @media (min-width: 481px) + tbody + tr + display: table-row + + td + padding: 16px + border-right: 1px solid $gray-200 + border-bottom: 1px solid $gray-200 + + html.dark & + border-color: rgba(255, 255, 255, 0.1) + + &:last-child + border-right: none + + strong + display: block + font-size: 12px + color: $gray-600 + margin-bottom: 8px + text-transform: uppercase + letter-spacing: 0.5px + + html.dark & + color: $gray-400 + + p + margin: 0 + font-size: 16px + font-weight: 600 + color: $gray-900 + + html.dark & + color: white + + .badge + margin-top: 8px + padding: 4px 12px + font-size: 14px + + // Tablet: stacked layout + @media (max-width: 768px) + tbody + tr + display: block + border-bottom: 1px solid $gray-200 + + html.dark & + border-color: rgba(255, 255, 255, 0.1) + + td + display: block + width: 100% + padding: 12px 16px + border: none + border-bottom: 1px solid $gray-100 + + html.dark & + border-color: rgba(255, 255, 255, 0.05) + + &:last-child + border-bottom: none + + strong + display: inline-block + font-size: 11px + color: $gray-600 + margin-right: 8px + text-transform: uppercase + letter-spacing: 0.5px + min-width: 80px + + html.dark & + color: $gray-400 + + p + display: inline + margin: 0 + font-size: 15px + font-weight: 600 + color: $gray-900 + + html.dark & + color: white + + .badge + display: inline-block + margin-left: 8px + padding: 3px 10px + font-size: 12px + + // Mobile: more compact stacked layout + @media (max-width: 480px) + tbody + tr + display: block + border-bottom: 1px solid $gray-200 + + html.dark & + border-color: rgba(255, 255, 255, 0.1) + + td + display: flex + align-items: center + justify-content: space-between + width: 100% + padding: 10px 12px + border: none + border-bottom: 1px solid $gray-100 + + html.dark & + border-color: rgba(255, 255, 255, 0.05) + + &:last-child + border-bottom: none + + strong + display: block + font-size: 10px + color: $gray-600 + text-transform: uppercase + letter-spacing: 0.5px + margin-bottom: 2px + + html.dark & + color: $gray-400 + + > div:first-child + flex: 1 + + p + display: inline + margin: 0 + font-size: 14px + font-weight: 600 + color: $gray-900 + + html.dark & + color: white + + .badge + display: inline-block + margin-left: 8px + padding: 2px 8px + font-size: 11px + + // Very small screens + @media (max-width: 360px) + tbody + tr + td + padding: 8px 10px + + strong + font-size: 9px + margin-bottom: 2px + + p + font-size: 13px + + .badge + padding: 2px 6px + font-size: 10px + + .margin_top_15 + text-align: center + display: flex + justify-content: center + align-items: center + + // Hide mini charts on mobile + @media (max-width: 768px) + svg + max-width: 100% + height: auto + + @media (max-width: 480px) + .mini_chart + margin-top: 6px !important + + svg + max-height: 40px + + .margin_top_15 + display: flex + justify-content: center + align-items: center diff --git a/app/assets/stylesheets/licenses.sass b/app/assets/stylesheets/licenses.sass index 7d3523756..cf2501388 100644 --- a/app/assets/stylesheets/licenses.sass +++ b/app/assets/stylesheets/licenses.sass @@ -1,15 +1,448 @@ -#license - h1 - margin-top: 20px - margin-bottom: 20px +// License show page +#license-show-page + padding: 24px 0 + margin-left: 24px + margin-right: 24px + + .license-show-breadcrumb + font-size: 18px + color: #6b7280 + margin-bottom: 16px + display: flex + align-items: center + gap: 6px + a + color: #5A2A82 + text-decoration: none + &:hover + text-decoration: underline + .license-show-sep + color: #d1d5db + .license-show-name + color: #374151 + font-weight: 500 + html.dark & + color: #9ca3af + a + color: #ffb91a + .license-show-sep + color: #4b5563 + .license-show-name + color: #d1d5db + + .license-show-header + display: flex + align-items: flex-start + justify-content: space-between + flex-wrap: wrap + gap: 12px + padding: 20px 24px 16px + border-bottom: 1px solid #e5e7eb + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + + .license-show-title + font-size: 22px + font-weight: 700 + color: #111827 + margin: 0 + display: flex + align-items: center + gap: 10px + word-break: break-word + .fa + color: #5A2A82 + font-size: 18px + flex-shrink: 0 + html.dark & + color: #f3f4f6 + .fa + color: #ffb91a + + .license-show-actions + display: flex + align-items: center + flex-wrap: wrap + gap: 8px + flex-shrink: 0 - .licenses_button_groups + .license-action-btn + display: inline-flex + align-items: center + gap: 5px + border-radius: 8px + font-size: 13px + + .license-show-body + padding: 24px + + .license-description + font-size: 15px + color: #374151 + line-height: 1.7 + margin: 0 + p + margin-bottom: 12px + &:last-child + margin-bottom: 0 + html.dark & + color: #d1d5db + + .license-no-description + display: flex + align-items: center + gap: 8px + font-size: 14px + color: #6b7280 + font-style: italic + .fa + color: #9ca3af + a + color: #5A2A82 + &:hover + text-decoration: underline + html.dark & + color: #9ca3af + a + color: #ffb91a + + .license-url-block + display: flex + align-items: baseline + flex-wrap: wrap + gap: 6px + margin-top: 20px + padding-top: 16px + border-top: 1px solid #f3f4f6 + font-size: 14px + color: #6b7280 + .fa + color: #9ca3af + font-size: 13px a - font-weight: normal !important + color: #5A2A82 + word-break: break-all + &:hover + text-decoration: underline + html.dark & + border-top-color: rgba(255, 255, 255, 0.06) + color: #9ca3af + a + color: #ffb91a + .license-show-footer + padding: 16px 24px + border-top: 1px solid #f3f4f6 + html.dark & + border-top-color: rgba(90, 42, 130, 0.2) + +@media (max-width: 575px) + #license-show-page + .license-show-header + flex-direction: column + .license-show-actions + width: 100% + .license-action-btn + flex: 1 + justify-content: center + +@media (max-width: 767px) #search-dingus margin-bottom: 0px !important #new_license .field_with_errors display: inline !important + +#license-edit-page + margin-left: 24px + margin-right: 24px + margin-top: 24px + + .license-edit-breadcrumb + font-size: 18px + color: #6b7280 + margin-bottom: 12px + display: flex + align-items: center + flex-wrap: wrap + gap: 6px + a + color: #5A2A82 + text-decoration: none + &:hover + text-decoration: underline + .license-edit-sep + color: #9ca3af + html.dark & + color: #9ca3af + a + color: #ffb91a + .license-edit-sep + color: #6b7280 + + .license-edit-header + margin-bottom: 20px + + .license-edit-title + font-size: 24px + font-weight: 700 + color: #111827 + margin: 0 + display: flex + align-items: center + gap: 10px + .fa + color: #5A2A82 + font-size: 20px + html.dark & + color: #f3f4f6 + .fa + color: #ffb91a + + label.checkbox + display: flex + align-items: center + gap: 6px + cursor: pointer + font-size: 14px + font-weight: 500 + color: #374151 + input[type="checkbox"] + flex-shrink: 0 + cursor: pointer + html.dark & + color: #d1d5db + + // Input fields — matches project basics form-control style + .form-control + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + -webkit-border-radius: 16px !important + -moz-border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + width: 85% !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + &::placeholder + color: #9ca3af !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #d1d5db !important + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + background: #ffffff !important + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &::placeholder + color: #6b7280 !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #6b7280 !important + &:focus, &:focus-visible + border-color: #ffb91a !important + background: #1D0631 !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + outline: none !important + + textarea.form-control.edit-description + height: 150px !important + resize: vertical + + // URL field prepend — add-on + input side by side + .license-url-prepend + display: flex !important + align-items: stretch !important + width: 85% !important + + .add-on + display: inline-flex !important + align-items: center + padding: 10px 12px + background: #f3f4f6 + border: 2px solid #e5e7eb + border-right: none + border-radius: 16px 0 0 16px + color: #6b7280 + font-size: 13px + white-space: nowrap + flex-shrink: 0 + html.dark & + background: #2D1548 + border-color: #5A2A82 + color: #9ca3af + + .form-control + border-radius: 0 16px 16px 0 !important + width: auto !important + flex: 1 1 0 !important + min-width: 0 + + // Mobile responsive + @media (max-width: 575px) + margin-left: 12px + margin-right: 12px + + .license-edit-breadcrumb + font-size: 13px + + .license-edit-title + font-size: 20px + + .control-group + margin-bottom: 16px + + .form-control + width: 100% !important + + .license-url-prepend + width: 100% !important + flex-wrap: nowrap + + .add-on + font-size: 11px + padding: 10px 8px + + .actions + display: flex + flex-direction: column + gap: 12px + align-items: stretch + + .btn-primary + width: 100% + + .pull-right + float: none !important + margin-right: 0 !important + display: flex + justify-content: flex-start + + label.checkbox + font-size: 13px + + @media (min-width: 576px) and (max-width: 767px) + margin-left: 16px + margin-right: 16px + + .form-control + width: 100% !important + + .license-url-prepend + width: 100% !important + +// Licenses index page +#licenses-index-page + padding: 24px 0 + margin-left: 24px + margin-right: 24px + + .licenses-page-header + display: flex + align-items: center + justify-content: space-between + flex-wrap: wrap + gap: 12px + margin-bottom: 24px + padding-bottom: 16px + border-bottom: 2px solid #e9ecef + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + + .licenses-page-title + font-size: 24px + font-weight: 700 + color: #111827 + margin: 0 + display: flex + align-items: center + gap: 10px + .fa + color: #5A2A82 + font-size: 20px + html.dark & + color: #f3f4f6 + .fa + color: #ffb91a + + .licenses-grid + display: grid + grid-template-columns: repeat(3, 1fr) + gap: 16px + margin-top: 16px + @media (max-width: 991px) + grid-template-columns: repeat(2, 1fr) + @media (max-width: 575px) + grid-template-columns: 1fr + + .license-card + padding: 16px 20px !important + transition: border-color 0.2s ease, box-shadow 0.2s ease + + .license-card-body + display: flex + align-items: center + gap: 10px + + .license-card-icon + color: #5A2A82 + font-size: 16px + flex-shrink: 0 + html.dark & + color: #ffb91a + + .license-card-link + font-size: 15px + font-weight: 500 + color: #111827 + text-decoration: none + overflow: hidden + text-overflow: ellipsis + white-space: nowrap + &:hover + color: #5A2A82 + text-decoration: none + html.dark & + color: #e5e7eb + &:hover + color: #ffb91a + + .licenses-empty-state + display: flex + flex-direction: column + align-items: center + padding: 48px 24px + text-align: center + .licenses-empty-icon + font-size: 48px + color: #d1d5db + margin-bottom: 16px + .licenses-empty-text + font-size: 15px + color: #6b7280 + margin-bottom: 8px + .licenses-empty-link + font-size: 14px + color: #6b7280 + a + color: #5A2A82 + &:hover + text-decoration: underline + html.dark & + .licenses-empty-icon + color: #4b5563 + .licenses-empty-text, + .licenses-empty-link + color: #9ca3af + .licenses-empty-link a + color: #ffb91a diff --git a/app/assets/stylesheets/newest_contributors_table.sass b/app/assets/stylesheets/newest_contributors_table.sass new file mode 100644 index 000000000..58c511d35 --- /dev/null +++ b/app/assets/stylesheets/newest_contributors_table.sass @@ -0,0 +1,77 @@ +// Newest Contributors Table Styling (Figma Design) +// This applies to all pages using _newest_contributions.html.haml partial + +table.table.table-striped.table-condensed + width: 100% + background: #ffffff !important + border-radius: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + overflow: hidden + transition: box-shadow 0.2s + padding: 0 24px + + @media (min-width: 768px) + padding: 0 32px + + html.dark & + background: #2D1548 !important + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + thead + tr + border-bottom: 1px solid #e5e7eb + + html.dark & + border-bottom: 1px solid rgba(90, 42, 130, 0.4) + + th + text-align: center + vertical-align: middle + font-size: 11px + font-weight: 600 + color: #6b7280 + padding: 8px + text-transform: uppercase + letter-spacing: 0.05em + border-bottom: none !important + + html.dark & + color: #9ca3af + + tbody + tr + border-bottom: 1px solid #f3f4f6 + transition: background-color 0.2s + background-color: transparent !important + + html.dark & + border-bottom: 1px solid rgba(90, 42, 130, 0.2) + background-color: transparent !important + + &:hover + background-color: #f9fafb !important + + html.dark & + background-color: rgba(255, 255, 255, 0.05) !important + + // Remove alternating row colors from Bootstrap striping + &:nth-child(odd) + background-color: transparent !important + + html.dark & + background-color: transparent !important + + &:hover + background-color: #f9fafb !important + + html.dark & + background-color: rgba(255, 255, 255, 0.05) !important + + td + text-align: center + padding: 10px + vertical-align: middle + border-bottom: none !important + + html.dark & + color: #e2e8f0 diff --git a/app/assets/stylesheets/oh-card.sass b/app/assets/stylesheets/oh-card.sass new file mode 100644 index 000000000..361f631e4 --- /dev/null +++ b/app/assets/stylesheets/oh-card.sass @@ -0,0 +1,45 @@ +// ────────────────────────────────────────────────────────────────────────────── +// .oh-card — Common card component (Figma design system) +// Replaces legacy Bootstrap .well with modern card styling +// ────────────────────────────────────────────────────────────────────────────── + +.oh-card + background: #ffffff + border-radius: 12px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + padding: 20px + margin-bottom: 16px + transition: border-color 0.2s, box-shadow 0.2s + + html.dark & + background: #2D1548 !important + border-color: rgba(90, 42, 130, 0.3) + color: #e2e8f0 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + // Variant: hoverable cards (e.g., review cards, link categories) + &--hoverable + cursor: pointer + &:hover + border-color: #5A2A82 + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12), 0 3px 6px rgba(0, 0, 0, 0.08) + + html.dark & + border-color: #ffb91a + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.32), 0 3px 6px rgba(0, 0, 0, 0.46) + + // Variant: flush (no padding — for tables or custom inner layouts) + &--flush + padding: 0 + + // Variant: compact (less padding for smaller cards) + &--compact + padding: 12px + + // Headings inside cards + h3, h4, h5 + margin-top: 0 + + // Tables inside cards + table + margin-bottom: 0 diff --git a/app/assets/stylesheets/oh-styles.sass b/app/assets/stylesheets/oh-styles.sass index bda95eea6..0ee861f0e 100644 --- a/app/assets/stylesheets/oh-styles.sass +++ b/app/assets/stylesheets/oh-styles.sass @@ -32,7 +32,10 @@ background-color: color-gradient($SECTION_BACKGROUND, 0) !important @mixin site-link-color - color: color-gradient($SECONDARY_SLATE_BLUE, 120) + color: #5A2A82 + + html.dark & + color: #ffb91a @mixin site-placeholder-color color: color-gradient($PRIMARY_DARK_GRAY, 120) @@ -55,13 +58,16 @@ @mixin top-navbar-colors color: white - background-color: #211e1e !important + background: linear-gradient(to right, #1D0631, #5A2A82) !important @mixin top-navbar-menu-colors - color: white + color: white !important + font-weight: 500 + transition: color 0.3s ease &:hover padding-bottom: 0px - color: color-gradient($SECONDARY_YELLOW, 60) + color: rgba(255, 255, 255, 0.7) !important + text-decoration: none @mixin twitter-background-color background-color: #5A2A82 @@ -119,15 +125,15 @@ border-left: 3px solid color-gradient($PRIMARY_DARK_GRAY, 100) @mixin footer-colors - color: color-gradient($PRIMARY_PURPLE, 120) !important - background-color: color-gradient($FOOTER_BACKGROUND, 0) !important + color: white !important + background: linear-gradient(to right, #1D0631, #5A2A82) !important a &:hover - color: color-gradient($FOOTER_BACKGROUND, 0) + color: white !important border-radius: 0 @mixin footer-copyright-colors - color: color-gradient($LIGHT_GRAY, 100) !important + color: rgba(156, 163, 175, 1) !important //////////////////////////////////////////////////////////////////// // @@ -264,6 +270,26 @@ &.no-border:hover border-color: color-gradient($LIGHT_GRAY, 80) !important +@mixin purple-theme-button-colors + color: white + background-color: #5A2A82 !important + border-color: #5A2A82 !important + &:hover + background-color: #4A1A72 !important + border-color: #4A1A72 !important + &.no-border:hover + border-color: #4A1A72 !important + +@mixin search-input-colors + background-color: #ffffff + color: #202020 + border-color: #d1d5db + &:focus + border-color: #5A2A82 + box-shadow: 0 0 0 3px rgba(90, 42, 130, 0.1) + &::placeholder + color: #9ca3af + @mixin join-now-button-colors color: white background-color: color-gradient($SECONDARY_FOREST_GREEN, 100) !important @@ -359,6 +385,53 @@ color: white background-color: color-gradient($SECONDARY_SLATE_BLUE, 120) !important +@mixin modern-pagination-styles + display: flex + align-items: center + justify-content: center + gap: 8px + margin-top: 32px + + .page-btn + min-width: 36px + width: auto + height: 36px + padding: 0 12px + border-radius: 8px + border: 1px solid #e5e7eb + background-color: #ffffff !important + color: #111827 + font-size: 14px + font-weight: 500 + cursor: pointer + transition: all 0.2s ease + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) + + html.dark & + border-color: rgba(255, 255, 255, 0.1) + background-color: #2D1548 !important + color: #ffffff + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1) + + &:hover:not(:disabled) + border-color: #5A2A82 + + html.dark & + border-color: #ffb91a + + &.active + background-color: #5A2A82 !important + color: #ffffff + border-color: transparent + + html.dark & + background-color: #ffb91a !important + color: #1D0631 + + &:disabled + opacity: 0.4 + cursor: not-allowed + //////////////////////////////////////////////////////////////////// // // Project mixins @@ -376,7 +449,7 @@ background-color: color-gradient($LIGHT_GRAY, 100) !important @mixin analysis-timestamp-color - color: color-gradient($LIGHT_GRAY, 120) + color: #ffffff @mixin licenses-permitted-color color: color-gradient($SECONDARY_FOREST_GREEN, 120) diff --git a/app/assets/stylesheets/ohloh_suggests.sass b/app/assets/stylesheets/ohloh_suggests.sass index 18f7423c1..3f36e4932 100644 --- a/app/assets/stylesheets/ohloh_suggests.sass +++ b/app/assets/stylesheets/ohloh_suggests.sass @@ -1,7 +1,4 @@ .recommendations - :padding .5em 6px - :background-color #eee - :border 2px solid #ccc h4.hide :margin-bottom .3em a @@ -32,8 +29,10 @@ :padding 2px p.description :margin-bottom 0 !important - :color #777 + :color #595959 :margin-left 39px + html.dark & + color: #d1d5db .title :margin 0 0 1px 39px .controls @@ -41,6 +40,8 @@ a#skip_all :float right :display block + :margin-left 8px a#more :float right :display block + :margin-left 8px diff --git a/app/assets/stylesheets/organizations.sass b/app/assets/stylesheets/organizations.sass index aa662895c..46f8d9de7 100644 --- a/app/assets/stylesheets/organizations.sass +++ b/app/assets/stylesheets/organizations.sass @@ -1,6 +1,471 @@ +// Organization Summary Container +.org-summary-container + max-width: 1280px + margin: 0 auto + padding: 24px 24px 0 + + @media (max-width: 640px) + padding: 0 + +// Organization Content Container +.org-content-container + max-width: 1280px + margin: auto 0 + padding: 24px + background: white + border-radius: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + border: 1px solid #f3f4f6 + + @media (max-width: 640px) + padding: 16px + + html.dark & + background: #2D1548 + border-color: rgba(90, 42, 130, 0.3) + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + h4 + margin-top: 0 !important + margin-bottom: 16px + font-size: 16px + font-weight: 700 + color: #1f2937 + + html.dark & + color: white + +.org-page-section + margin-bottom: 24px + +.org-summary-grid + display: grid + grid-template-columns: 2fr 1fr + gap: 24px + margin-bottom: 16px + + @media (max-width: 768px) + grid-template-columns: 1fr + gap: 12px + margin-bottom: 12px + +.org-summary-box + background: white + border-radius: 16px + margin: 0 0 16px + padding: 24px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + border: 1px solid #f3f4f6 + + html.dark & + background: #2D1548 + border-color: rgba(90, 42, 130, 0.3) + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + h4 + margin-top: 0 !important + margin-bottom: 16px + font-size: 16px + font-weight: 700 + color: #1f2937 + + html.dark & + color: white + + #org_summary + line-height: 1.6 + color: #374151 + + html.dark & + color: #d1d5db + + a + color: #5A2A82 + text-decoration: underline + + &:hover + color: #5A2A82 + + html.dark & + color: white + text-decoration: underline + + &:hover + color: #ffb91a + +// Organization Header Banner +.org-header-banner + background: white + border-bottom: 1px solid #e5e7eb + padding: 16px 0 + + html.dark & + background: linear-gradient(to bottom, #1a0d2e, #0D0019) + border-bottom-color: rgba(90, 42, 130, 0.4) + +.org-header-container + max-width: 1280px + margin: 0 auto + padding: 0 16px + +.org-header-content + display: flex + align-items: center + gap: 24px + flex-wrap: wrap + + @media (max-width: 640px) + flex-direction: column + align-items: flex-start + +.org-header-logo-section + flex-shrink: 0 + + img + width: 80px + height: 80px + border-radius: 16px + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) + + @media (min-width: 640px) + width: 88px + height: 88px + + html.dark & + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3) + +.org-header-info + flex: 1 + min-width: 0 + +.org-header-title + margin-bottom: 8px + + h1 + margin: 0 + font-size: 30px + font-weight: 700 + color: #1f2937 + + @media (min-width: 640px) + font-size: 36px + + a + color: inherit + text-decoration: none + + &:hover + color: #5A2A82 + + html.dark & + color: white + + a:hover + color: #ffb91a + +.org-header-stats + display: flex + flex-wrap: wrap + gap: 16px + font-size: 14px + color: #6b7280 + + html.dark & + color: #9ca3af + + .stat-item + display: flex + align-items: center + gap: 2px + + icon + font-size: 14px + opacity: 0.75 + +.org-header-actions + flex-shrink: 0 + +.org-header-settings-btn + display: inline-flex + align-items: center + gap: 8px + padding: 8px 16px + background: #f3f4f6 + color: #374151 + border: 1px solid #d1d5db + border-radius: 8px + font-size: 14px + font-weight: 500 + text-decoration: none + transition: all 0.2s + + &:hover + background: #e5e7eb + color: #1f2937 + + html.dark & + background: #2D1548 + color: #d1d5db + border-color: rgba(90, 42, 130, 0.4) + + &:hover + background: #3a1a5e + color: #f3f4f6 + + icon + font-size: 16px + +// SASS Variables (mirrors projects index design system) +$org-primary-color: #5A2A82 +$org-primary-hover: rgba(90, 42, 130, 0.9) +$org-bg-color: #f9fafb +$org-card-bg: #ffffff +$org-text-primary: #1f2937 +$org-text-secondary: #6b7280 +$org-border-color: rgba(229, 231, 235, 0.8) +$org-shadow-sm: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) +$org-shadow-md: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23) +$org-dark-primary: #ffb91a +$org-dark-bg: #1D0631 +$org-dark-card: #2D1548 +$org-dark-text-primary: #ffffff +$org-dark-text-secondary: #d1d5db +$org-dark-border: rgba(255, 255, 255, 0.1) +$org-dark-shadow-sm: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) +$org-dark-shadow-md: 0 3px 6px rgba(0,0,0,0.32), 0 3px 6px rgba(0,0,0,0.46) + +// Organizations Index Page +#organizations_index_page + background-color: $org-bg-color + min-height: 100vh + padding: 32px 0 + transition: background-color 0.3s ease, color 0.3s ease + html.dark & + background-color: $org-dark-bg + + .organizations-container + max-width: 1280px + margin: 0 auto + padding: 0 16px + + .organizations-header + display: flex + justify-content: space-between + align-items: center + margin-bottom: 24px + flex-wrap: wrap + gap: 16px + + h1 + font-size: 30px + font-weight: bold + color: $org-text-primary + margin: 0 + html.dark & + color: $org-dark-text-primary + + @media (min-width: 640px) + font-size: 36px + + .header-actions + display: flex + gap: 12px + align-items: center + flex-wrap: wrap + + .btn-add-organization + padding: 8px 16px + border-radius: 8px + font-weight: 600 + font-size: 14px + transition: all 0.2s ease + text-decoration: none + display: inline-flex + align-items: center + gap: 8px + white-space: nowrap + background-color: $org-primary-color + color: #ffffff + border: none + box-shadow: $org-shadow-md + &:hover + background-color: $org-primary-hover + color: #ffffff + html.dark & + background-color: $org-dark-primary + color: #1D0631 !important + &:hover + background-color: rgba(255, 185, 26, 0.9) + + .no-results + color: $org-text-secondary + padding: 24px 0 + html.dark & + color: $org-dark-text-secondary + +// Organization Card +.organization-card + background-color: $org-card-bg + border-radius: 12px + border: 1px solid $org-border-color + padding: 20px 24px + margin-bottom: 16px + box-shadow: $org-shadow-sm + transition: all 0.3s ease + html.dark & + background-color: $org-dark-card + border-color: $org-dark-border + box-shadow: $org-dark-shadow-sm + + &:hover + border-color: $org-primary-color + box-shadow: $org-shadow-md + html.dark & + border-color: $org-dark-primary + box-shadow: $org-dark-shadow-md + + .organization-header + margin-bottom: 16px + + .organization-title-section + h2 + margin: 0 0 4px 0 + font-size: 18px + font-weight: 600 + + @media (min-width: 640px) + font-size: 20px + + a + color: $org-primary-color + text-decoration: none + &:hover + text-decoration: underline + html.dark & + color: $org-dark-primary !important + + .organization-content + display: grid + grid-template-columns: 1fr auto + column-gap: 24px + + @media (max-width: 768px) + grid-template-columns: 1fr + + .organization-main + .organization-icon-desc + display: flex + gap: 12px + + .organization-description + flex: 1 + font-size: 14px + color: $org-text-secondary + line-height: 1.5 + word-wrap: break-word + html.dark & + color: $org-dark-text-secondary + + .organization-stats + min-width: 200px + border-left: 1px solid $org-border-color + padding-left: 24px + display: flex + flex-direction: column + gap: 12px + html.dark & + border-color: $org-dark-border + + @media (max-width: 768px) + border-left: none + border-top: 1px solid $org-border-color + padding-left: 0 + padding-top: 16px + margin-top: 16px + flex-direction: row + flex-wrap: wrap + + .stat-item + display: flex + align-items: center + gap: 10px + + .stat-value + font-size: 16px + font-weight: bold + color: $org-primary-color + html.dark & + color: $org-dark-primary + + a + color: inherit + text-decoration: none + &:hover + text-decoration: underline + + @media (min-width: 640px) + font-size: 18px + + .stat-label + font-size: 12px + color: $org-text-secondary + html.dark & + color: $org-dark-text-secondary + + @media (min-width: 640px) + font-size: 14px + +// New Organization page styles — radio button alignment +.oh-card .radio-inline + display: inline-flex + align-items: center + gap: 6px + padding-left: 0 + vertical-align: middle + cursor: pointer + + input[type="radio"] + position: static + margin: 0 + flex-shrink: 0 + vertical-align: middle + +.org-new-title + font-weight: bold !important + margin-top: 24px + margin-bottom: 16px + color: $org-text-primary + html.dark & + color: $org-dark-text-primary + +// URL prefix add-on (http://www.openhub.net/orgs/) — scoped to org new/edit form only +.org-form .input-prepend .add-on + html.dark & + background-color: #2D1548 !important + border-color: rgba(255, 255, 255, 0.15) !important + color: #ffb91a !important + .project_activity_margin_top margin-top: -8px +.org-data-table + table.table.table-striped.table-condensed + thead th + border-bottom: 1px solid #888 !important + + html.dark & + border-bottom-color: #5A2A82 !important + +table.table.table-striped.table-condensed + thead th + border-bottom: 1px solid #888 !important + + html.dark & + border-bottom-color: #5A2A82 !important + + td:first-child + text-align: left + tbody.portfolio-projects-table td .delta @@ -8,14 +473,488 @@ tbody.portfolio-projects-table tr:nth-child(odd), tr:nth-child(even) td background-color: white + + html.dark & + background-color: #2D1548 tr:nth-child(3n+1) td border-top: solid 1px #ddd !important border-bottom: solid 1px #ddd !important + + html.dark & + border-top-color: #5A2A82 !important + +// Portfolio Projects List +#portfolio_projects_list + max-width: 1280px + margin: 0 auto + +// Portfolio Projects Mobile Card View +.portfolio-mobile-view + display: block + + @media (min-width: 768px) + display: none + +.portfolio-desktop-view + display: none + + @media (min-width: 768px) + display: table + +.portfolio-project-card + background: white + border-radius: 12px + padding: 16px + margin-bottom: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) + + html.dark & + background: #2D1548 + border: 1px solid rgba(90, 42, 130, 0.3) + + .card-header + display: flex + align-items: flex-start + justify-content: space-between + gap: 12px + margin-bottom: 16px + + .project-info + flex: 1 + + .project-name + font-size: 14px + font-weight: 600 + color: #5A2A82 + text-decoration: none + display: block + margin-bottom: 6px + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + .project-meta + display: flex + align-items: center + gap: 8px + + a[class*='twenty_project_activity_level'] + position: static !important + display: inline-block !important + margin-left: 0 !important + top: auto !important + vertical-align: middle + flex-shrink: 0 + + .activity-indicator + display: flex + align-items: center + + .use-this-count + text-align: center + + .count-value + font-size: 16px + font-weight: 700 + color: #1f2937 + + html.dark & + color: white + + .count-label + font-size: 10px + color: #6b7280 + + html.dark & + color: #9ca3af + + .card-stats + display: grid + grid-template-columns: 1fr 1fr + gap: 8px + + .stat-box + background: #f9fafb + border-radius: 8px + padding: 12px + + html.dark & + background: rgba(29, 6, 49, 0.4) + + .stat-title + font-size: 11px + font-weight: 600 + color: #6b7280 + margin-bottom: 8px + + html.dark & + color: #d1d5db + + .stat-rows + display: flex + flex-direction: column + gap: 4px + + .stat-row + display: flex + justify-content: space-between + align-items: center + font-size: 12px + + .stat-label + color: #6b7280 + font-size: 11px + + html.dark & + color: #9ca3af + +// Affiliated Committers Mobile View +.affiliated-committers-mobile-view + display: block + + @media (min-width: 768px) + display: none + +.affiliated-committers-desktop-view + display: none + + @media (min-width: 768px) + display: table + +.committer-card + background: white + border-radius: 12px + padding: 16px + margin-bottom: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) + + html.dark & + background: #2D1548 + border: 1px solid rgba(90, 42, 130, 0.3) + + .card-header + display: flex + align-items: flex-start + gap: 12px + margin-bottom: 16px + padding-bottom: 12px + border-bottom: 1px solid #e5e7eb + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + + .committer-info + display: flex + gap: 12px + flex: 1 + + .committer-avatar + border-radius: 50% + flex-shrink: 0 + + .committer-details + flex: 1 + + .committer-name + font-size: 14px + font-weight: 600 + color: #5A2A82 + text-decoration: none + display: block + margin-bottom: 6px + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + .you-badge + font-size: 11px + color: #6b7280 + margin-left: 4px + + .committer-badges + display: flex + flex-direction: column + gap: 6px + + .badge-item + display: flex + align-items: center + gap: 6px + + .badge-label + font-size: 10px + color: #6b7280 + font-weight: 600 + text-transform: uppercase + min-width: 50px + + html.dark & + color: #9ca3af + + .pips + margin: 0 !important + + .mini-badges-section + margin: 0 + position: relative + height: auto + + .card-stats + display: flex + flex-direction: column + gap: 16px + + .stat-group + .stat-label + font-size: 11px + font-weight: 600 + color: #6b7280 + margin-bottom: 8px + display: block + + html.dark & + color: #9ca3af + + .stat-values + display: flex + flex-direction: column + gap: 8px + + .stat-item + display: flex + flex-direction: column + gap: 4px + font-size: 12px + + .stat-sublabel + color: #374151 + font-size: 12px + font-weight: 700 + text-transform: uppercase + letter-spacing: 0.5px + + html.dark & + color: #e5e7eb + + .stat-value + color: #1f2937 + font-weight: 700 + font-size: 15px + + html.dark & + color: white + + .stat-project-info + display: flex + align-items: center + gap: 20px + padding: 8px + border-radius: 8px + margin-top: 4px + + .project-icon + flex-shrink: 0 + + .project-details + flex: 1 + display: flex + flex-direction: column + gap: 2px + + .project-name + font-size: 12px + color: #5A2A82 + text-decoration: none + font-weight: 500 + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + .commit-count, .commit-date + font-size: 11px + color: #6b7280 + + html.dark & + color: #9ca3af + +// Outside Committers Mobile View +.outside-committers-mobile-view + display: block + + @media (min-width: 768px) + display: none + +.outside-committers-desktop-view + display: none + + @media (min-width: 768px) + display: table + + .stat-subgroup + display: flex + flex-direction: column + gap: 8px + + .stat-item + display: flex + flex-direction: column + gap: 4px + + .stat-sublabel + font-size: 12px + font-weight: 700 + color: #374151 + text-transform: uppercase + letter-spacing: 0.5px + + html.dark & + color: #e5e7eb + + .project-list + .project-link + color: #5A2A82 + text-decoration: none + font-size: 12px + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + .stat-value + color: #1f2937 + font-weight: 700 + font-size: 15px + + html.dark & + color: white + +// Outside Projects Mobile View +.outside-projects-mobile-view + display: block + + @media (min-width: 768px) + display: none + +.outside-projects-desktop-view + display: none + + @media (min-width: 768px) + display: table + +.outside-project-card + background: white + border-radius: 12px + padding: 16px + margin-bottom: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) + + html.dark & + background: #2D1548 + border: 1px solid rgba(90, 42, 130, 0.3) + + .card-header + margin-bottom: 16px + padding-bottom: 12px + border-bottom: 1px solid #e5e7eb + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + + .project-info + .project-name + font-size: 14px + font-weight: 600 + color: #5A2A82 + text-decoration: none + display: block + margin-bottom: 8px + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + .project-meta + display: flex + align-items: center + gap: 8px + + a[class*='twenty_project_activity_level'] + position: static !important + display: inline-block !important + margin-left: 0 !important + top: auto !important + vertical-align: middle + flex-shrink: 0 + + .card-stats + display: flex + flex-direction: column + gap: 12px + + .stat-box + display: flex + flex-direction: column + gap: 8px + + .stat-item + display: flex + flex-direction: column + gap: 4px + + .stat-label + font-size: 11px + font-weight: 600 + color: #6b7280 + + html.dark & + color: #9ca3af + + .stat-sublabel + font-size: 10px + color: #6b7280 + + html.dark & + color: #9ca3af + + .stat-value + font-size: 12px + color: #1f2937 + font-weight: 600 + + html.dark & + color: white + + a + color: #5A2A82 + text-decoration: none + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + border-bottom-color: #5A2A82 !important tr:nth-child(6n+1), tr:nth-child(6n+2), tr:nth-child(6n+3) td background-color: #f9f9f9 !important + html.dark & + background-color: #2D1548 !important + .org_project_rating margin-left: 18px display: block @@ -30,155 +969,727 @@ tbody.portfolio-projects-table .language_name line-height: 28px + padding: 0 8px + + html.dark & + color: white !important #org_icon margin-bottom: 15px margin-top: 10px .org-summary-quick-ref line-height: 20px - margin-top: -20px !important + margin-top: 0 !important + h4 - margin-top: 0px !important + margin-top: 0 !important + margin-bottom: 16px + font-size: 16px + font-weight: 700 + color: #1f2937 + + html.dark & + color: white + dl - margin-bottom: 0px !important + margin-bottom: 0 !important + + dt + color: #6b7280 + font-size: 12px + font-weight: 600 + text-transform: uppercase + margin-bottom: 4px + + html.dark & + color: #9ca3af + + dd + color: #374151 + margin-bottom: 12px + + html.dark & + color: #d1d5db + + a + color: #5A2A82 + text-decoration: none + + &:hover + text-decoration: underline + + html.dark & + color: #ffb91a + +@media (min-width: 768px) and (max-width: 1024px) + .org-summary-quick-ref + .dl-horizontal + dt + width: 90px !important + font-size: 11px !important + dd + margin-left: 110px !important + word-break: break-word + +@media (max-width: 576px) + .manage-projects-table + thead + display: none + tbody tr + display: block + border: 1px solid #e5e7eb + border-radius: 8px + margin-bottom: 12px + overflow: hidden + background: white + + html.dark & + background: #2D1548 + border-color: rgba(90, 42, 130, 0.3) + + tbody td + display: flex + align-items: center + justify-content: space-between + padding: 8px 12px + border-bottom: 1px solid #f3f4f6 + border-top: none + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.2) + + &::before + content: attr(data-label) + font-weight: 600 + font-size: 11px + color: #6b7280 + text-transform: uppercase + margin-right: 12px + flex-shrink: 0 + + html.dark & + color: #9ca3af + + &:last-child + border-bottom: none + +@media (max-width: 767px) + .org-summary-quick-ref + .dl-horizontal + dt + float: none !important + width: auto !important + text-align: left !important + white-space: normal !important + dd + margin-left: 0 !important + word-break: break-word /* ORG -- Infographics -- START */ +// Organization Infographic - Modern Design #org_infographic - position: relative - height: 290px + background: linear-gradient(to bottom right, #f9fafb, #f3f4f6) + border-radius: 12px + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06) + overflow: hidden + + html.dark & + background: linear-gradient(to bottom right, #1a1632, #0f0b1f) + + img + html.dark & + filter: invert(1) + + // All links in infographic + a + color: #5A2A82 + text-decoration: none + + &:hover + text-decoration: underline + + html.dark & + color: #ffb91a + .about font-size: 12px - color: #80539C + color: #5A2A82 + font-weight: 500 + + html.dark & + color: #ffb91a + .pictogram_link_active - color: #000 !important + color: #5A2A82 !important pointer-events: none cursor: default text-decoration: none !important + + html.dark & + color: #ffb91a !important + .disabled - color: #DDDAD9 !important + color: #000 !important + + html.dark & + color: #ffffff !important + .active - color: #2a2a2c !important + color: #374151 !important background-color: transparent !important + + html.dark & + color: #d1d5db !important + .inactive - color: #646E81 !important + color: #6b7280 !important + + html.dark & + color: #9ca3af !important + .icon-group - font-size: 45px - color: #595A5D + font-size: 48px + color: #000000 + + html.dark & + color: #ffffff + +// Full-width header bar +.infographic-header + background: linear-gradient(to right, #7B68A6, #6B5896) + color: white + text-align: center + font-size: 18px + font-weight: 700 + padding: 12px 16px + letter-spacing: 0.5px + + @media (min-width: 640px) + font-size: 20px + padding: 16px 24px + +// Main content area with 3 columns +.infographic-main + display: flex + flex-direction: column + align-items: center + gap: 16px + padding: 20px 16px + + @media (min-width: 640px) + gap: 24px + padding: 24px 20px + + @media (min-width: 992px) + flex-direction: row + justify-content: center + align-items: center + gap: 0 + padding: 32px 16px + + & > * + @media (min-width: 992px) + display: flex + align-items: center + flex-shrink: 0 + +#org_infographic #section1 - position: absolute - width: 250px - left: 20px - height: 215px + display: flex + flex-direction: column + align-items: center + gap: 8px + text-align: center + flex-shrink: 0 + max-width: 280px + + @media (min-width: 992px) + flex-direction: row + align-items: center + gap: 0 + max-width: none + .col1 - width: 30% text-align: center - margin-top: 65px - font-weight: bold - color: #195887 + font-weight: 700 + color: #000000 !important + font-size: 12px + line-height: 1.4 + min-width: 70px + display: flex + flex-direction: column + align-items: center + gap: 8px + + @media (min-width: 992px) + font-size: 14px + min-width: 80px + + html.dark & + color: #ffffff !important + + a + color: #000000 !important + text-decoration: none + + &:hover + color: #5A2A82 !important + text-decoration: underline + + html.dark & + color: #ffffff !important + + &:hover + color: #ffb91a !important + + .icon-group + font-size: 40px + margin-bottom: 8px + display: block + color: #000000 !important + + @media (min-width: 992px) + font-size: 48px + + html.dark & + color: #ffffff !important + + .commits-text + text-align: center + line-height: 1.5 + margin-bottom: 8px + background: white + border: 1px solid #e5e7eb + border-radius: 8px + padding: 10px 12px + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) + + @media (min-width: 992px) + padding: 12px 16px + + html.dark & + background: #1D0631 + border-color: rgba(90, 42, 130, 0.3) + + .commits + font-size: 12px + font-weight: 700 + margin-bottom: 4px + color: #000000 + + @media (min-width: 992px) + font-size: 14px + + html.dark & + color: #ffffff + + .projects + font-size: 10px + color: #4b5563 + line-height: 1.4 + + @media (min-width: 992px) + font-size: 12px + + html.dark & + color: #d1d5db + .col2 - width: 70% - padding-top: 120px - line-height: 13px text-align: center - .commits - font-size: 13px - .projects - font-size: 11px + line-height: 1.5 + display: flex + flex-direction: column + align-items: center + gap: 12px + padding-top: 0 !important + + .svg-arrow + flex-shrink: 0 #section2 - position: absolute - width: 270px - right: 20px - height: 215px - top: 0 + display: flex + flex-direction: column + align-items: center + gap: 8px + text-align: center + flex-shrink: 0 + max-width: 280px + + @media (min-width: 992px) + flex-direction: row + align-items: center + gap: 0 + max-width: none + .col1 - width: 65% - padding-top: 121px - line-height: 13px text-align: center - .commits - font-size: 13px - .projects - font-size: 11px + line-height: 1.5 + display: flex + flex-direction: column + align-items: center + gap: 12px + padding-top: 0 !important + + .svg-arrow + flex-shrink: 0 + .col2 - width: 30% text-align: center - margin-top: 65px - font-weight: bold - color: #000 + font-weight: 700 + color: #000000 !important + font-size: 12px + line-height: 1.4 + min-width: 70px + display: flex + flex-direction: column + align-items: center + gap: 8px + + @media (min-width: 992px) + font-size: 14px + min-width: 80px + + html.dark & + color: #ffffff !important + + a + color: #000000 !important + text-decoration: none + + &:hover + color: #5A2A82 !important + text-decoration: underline + + html.dark & + color: #ffffff !important + + &:hover + color: #ffb91a !important + + .icon-group + font-size: 40px + margin-bottom: 8px + display: block + color: #000000 !important + + @media (min-width: 992px) + font-size: 48px + + html.dark & + color: #ffffff !important + + .commits-text + text-align: center + line-height: 1.5 + background: white + border: 1px solid #e5e7eb + border-radius: 8px + padding: 10px 12px + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) + + @media (min-width: 992px) + padding: 12px 16px + + html.dark & + background: #1D0631 + border-color: rgba(90, 42, 130, 0.3) + + .commits + font-size: 12px + font-weight: 700 + margin-bottom: 4px + color: #000000 + + @media (min-width: 992px) + font-size: 14px + + html.dark & + color: #ffffff + + .projects + font-size: 10px + color: #4b5563 + line-height: 1.4 + + @media (min-width: 992px) + font-size: 12px + + html.dark & + color: #d1d5db + span - line-height: 15px + line-height: 1.4 font-size: 13px + display: block - +// Infographic Box - Center Component #infographic_box - width: 500px - margin: 0 auto - letter-spacing: -0.5px + flex: 1 + min-width: 0 + display: inline-table + flex-direction: column + background: white + border-radius: 12px + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06) + border: 2px solid #e5e7eb + width: 100% + max-width: 420px + + @media (min-width: 992px) + flex: 1 + max-width: none + + html.dark & + background: #1D0631 + border-color: rgba(90, 42, 130, 0.4) + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.2) + a.print_infographic font-style: normal !important - font-size: 11px - .header - @include infographic-box-header-background-color - -webkit-print-color-adjust: exact - height: 33px - color: white !important - -webkit-print-color-adjust: exact - display: table-cell - vertical-align: middle - text-align: center - font-size: 18px - width: inherit + font-size: 12px + color: #5A2A82 + font-weight: 500 + display: inline-flex + align-items: center + gap: 6px + + &:hover + text-decoration: underline + + html.dark & + color: #ffb91a + .content - background-color: #d2dae6 !important - -webkit-print-color-adjust: exact - height: 185px - padding: 15px 50px - .commits - font-size: 13px - .projects - font-size: 11px - .col1 - width: 30% - .col2 - width: 33% - padding-top: 64px - line-height: 13px - .col3 - width: 34% + padding: 16px 12px + display: flex + flex-direction: column + align-items: center + justify-content: center + gap: 12px + + @media (min-width: 640px) + padding: 20px 16px + gap: 14px + + @media (min-width: 992px) + flex-direction: row + justify-content: space-between + padding: 24px 16px + gap: 16px + + .col1, .col3 + display: flex + flex-direction: column + align-items: center + gap: 8px text-align: center - .icon-group - font-size: 55px - display: inline-block - margin-top: 17px + flex: 1 + + .col2 + display: flex + align-items: center + justify-content: center + padding: 0 8px + flex: 0 0 auto + .count - @include site-link-color - font-size: 22px - text-align: center + font-size: 28px + font-weight: 700 + color: #5A2A82 + line-height: 1.2 + + @media (min-width: 992px) + font-size: 32px + + html.dark & + color: #ffb91a + a - @include site-link-color + color: inherit + text-decoration: none + display: flex + flex-direction: column + align-items: center + gap: 4px + + &:hover + text-decoration: underline + + .portfolio_label + color: inherit + font-size: 11px + font-weight: 600 + white-space: nowrap + .portfolio_label - @include site-link-color - font-size: 12px + font-size: 10px + font-weight: 600 + color: #000000 white-space: nowrap - margin-top: -5px + + @media (min-width: 992px) + font-size: 11px + + html.dark & + color: #ffffff + + .icon-group + font-size: 36px + color: #000000 + display: block + margin-bottom: 4px + + @media (min-width: 992px) + font-size: 40px + + html.dark & + color: #ffffff + + .portfolio-stats + text-align: center + padding: 12px 16px + + @media (min-width: 992px) + padding: 16px 24px + + border-top: 1px solid #e5e7eb + + html.dark & + border-top-color: rgba(90, 42, 130, 0.3) + + .commits + font-size: 12px + font-weight: 700 + color: #000000 + margin-bottom: 4px + + @media (min-width: 992px) + font-size: 14px + + html.dark & + color: #ffffff + + .projects + font-size: 11px + color: #4b5563 + + @media (min-width: 992px) + font-size: 12px + + html.dark & + color: #d1d5db + .footer_line - @include site-link-color - font-size: 1px - height: 7px !important - .bottom_links - .col1 - width: 63px !important - img - max-width: 100% - .col2 - margin-left: 60px - width: 217px + height: 4px + background: linear-gradient(to right, #7B68A6, #5A2A82) + +// Infographic Footer - Full Width +.infographic-footer + display: grid + grid-template-columns: 1fr + gap: 12px + padding: 12px 16px + border-top: 1px solid #d1d5db + align-items: center + text-align: center + + @media (min-width: 640px) + grid-template-columns: auto 1fr auto + text-align: left + gap: 16px + padding: 12px 24px + + html.dark & + border-top-color: rgba(90, 42, 130, 0.3) + + .footer-col1 + display: flex + justify-content: center + + @media (min-width: 640px) + justify-content: flex-start + + img + max-width: 88px + height: 31px + + .footer-col2 + font-size: 12px + text-align: center + + a + color: #5A2A82 + font-weight: 500 + + &:hover + text-decoration: underline + + html.dark & + color: #ffb91a + + .footer-col3 + display: flex + flex-direction: column + align-items: center + gap: 8px + + @media (min-width: 640px) + align-items: flex-end + + .about + font-size: 12px + color: #6b7280 + font-weight: 500 + + html.dark & + color: #9ca3af + + a.print_infographic font-size: 12px - .col3 - width: 160px - .hub_icon - margin-top: 7px + color: #5A2A82 + font-weight: 500 + display: inline-flex + align-items: center + gap: 6px + + &:hover + text-decoration: underline + + html.dark & + color: #ffb91a + +// SVG Arrow Styling +.svg-arrow + display: flex + align-items: center + justify-content: center + min-height: 40px + + // Mobile: Rotate arrows to vertical for stacked layout + @media (max-width: 991px) + min-height: 90px + + svg + transform: rotate(90deg) scale(0.7) + transform-origin: center + + svg + fill: #000000 !important + stroke: #000000 !important + color: #000000 !important + + path + fill: #000000 !important + stroke: #000000 !important + + html.dark & + fill: #ffffff !important + stroke: #000 !important + color: #ffffff !important + + path + fill: #ffffff !important + stroke: #000 !important .infographic_links_in_print text-decoration: none !important @@ -206,6 +1717,53 @@ form#new_logo p word-wrap: break-word +#addthis_sharing + h4 + margin-top: 0 !important + margin-bottom: 16px + font-size: 16px + font-weight: 700 + color: #1f2937 + + html.dark & + color: white + +// Pagination button for "See All" links +.pagination-button-container + text-align: center + +.btn-see-all-org + display: inline-flex + align-items: center + justify-content: center + gap: 8px + padding: 12px 32px + background: $org-primary-color + color: white !important + border: none + border-radius: 8px + font-size: 14px + font-weight: 600 + text-decoration: none + transition: all 0.2s ease + box-shadow: $org-shadow-sm + min-width: 200px + + &:hover + background: $org-primary-hover + color: white !important + box-shadow: $org-shadow-md + text-decoration: none + transform: translateY(-1px) + + html.dark & + background: $org-dark-primary + color: #1D0631 !important + + &:hover + background: rgba(255, 185, 26, 0.9) + color: #1D0631 !important + @media print a[href]:after content: none !important diff --git a/app/assets/stylesheets/orgs.sass b/app/assets/stylesheets/orgs.sass index da69909ae..39ccb2efe 100644 --- a/app/assets/stylesheets/orgs.sass +++ b/app/assets/stylesheets/orgs.sass @@ -1,11 +1,38 @@ #explore_orgs_page h1 - margin-left: -10px + font-size: 36px + font-weight: 700 + color: #1a202c + margin: 0 + letter-spacing: normal + line-height: 1.2 + + html.dark & + color: #ffffff + + h2 + font-weight: 700 !important + color: #1a202c !important + margin: 0 0 16px 0 !important + line-height: 1.2 !important + + html.dark & + color: #ffffff !important + #orgs_by_30_days_volume label#filter margin-top: 4px + td + vertical-align: middle + &:first-child + text-align: left + padding-left: 12px td a line-height: 29px + &.logo + display: inline-block + line-height: 0 + vertical-align: middle .busy position: absolute top: 185px @@ -13,58 +40,565 @@ width: 16px height: 16px #header th - border-top: 1px solid #ddd border-bottom: 0px - background-color: #f9f9f9 - tbody tr + background-color: #5b5d6237 + color: #000 + padding: 10px 12px + + html.dark & + background-color: #3A1D58 + color: #ffffff + + .sort-section + display: flex + align-items: center + gap: 8px + margin-bottom: 5px + + label + font-size: 14px + color: #6b7280 + white-space: nowrap + + html.dark & + color: #d1d5db + + .custom-sort-dropdown + position: relative + display: inline-block + + .sort-dropdown-btn + display: flex + align-items: center + gap: 8px + height: 40px + padding: 0 12px + background-color: #f9fafb + border: 1px solid #d1d5db + border-radius: 8px + color: #111827 + font-size: 14px + font-weight: 400 + min-width: 180px + cursor: pointer + transition: background 0.15s ease, border-color 0.15s ease + white-space: nowrap + box-sizing: border-box + + &:hover + background-color: #f3f4f6 + border-color: #5A2A82 + + html.dark & + background-color: #1D0631 + border-color: #5A2A82 + color: #ffffff + + &:hover + background-color: rgba(29, 6, 49, 0.8) + border-color: #ffb91a + + .selection + flex: 1 + text-align: left + + i + font-size: 12px + transition: transform 0.2s ease + + &.open + .sort-dropdown-menu + display: block + + .sort-dropdown-menu + display: none + position: absolute + top: 100% + padding: 0 + left: 0 + margin-top: 4px + min-width: 180px + background-color: #ffffff + border: 1px solid #d1d5db + border-radius: 8px + overflow: hidden + z-index: 1000 + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23) + + html.dark & + background-color: #1D0631 + border-color: #5A2A82 + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.38), 0 6px 6px rgba(0, 0, 0, 0.46) + + .sort-dropdown-item + display: block + width: 100% + padding: 8px 16px + color: #111827 + font-size: 14px + font-weight: 400 + text-align: left + text-decoration: none + background: transparent + border: none + box-sizing: border-box + transition: background 0.15s ease, color 0.15s ease + cursor: pointer + + &:hover + background: #f3f4f6 + + &.active + background-color: #5A2A82 !important + color: #ffffff + font-weight: 400 + + &:hover + background-color: #5A2A82 + + html.dark & + color: #ffffff + + &:hover + background: rgba(255, 255, 255, 0.1) + + // Desktop Header (visible on both mobile and desktop) + .desktop-header + display: block + + // Mobile Container + .mobile-org-container + display: block + + @media (min-width: 768px) + display: none + + .orgs-card + background: #ffffff + border-radius: 16px + overflow: hidden + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + transition: box-shadow 0.3s ease + + html.dark & + background: #2D1548 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + &:hover + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23) + + html.dark & + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.32), 0 3px 6px rgba(0, 0, 0, 0.46) + + .card-body-mobile + padding: 16px + display: flex + flex-direction: column + gap: 12px + + .org-card + background: #f9fafb + border: 1px solid #e5e7eb + border-radius: 8px + padding: 12px + margin-bottom: 12px + + html.dark & + background: rgba(29, 6, 49, 0.5) + border-color: rgba(90, 42, 130, 0.3) + &:last-child - border-bottom: 1px solid #ddd - &:nth-child(even) - background-color: #f9f9f9 + margin-bottom: 0 + + .org-card-header + display: flex + align-items: center + gap: 8px + margin-bottom: 12px + + .org-card-icon + flex-shrink: 0 + line-height: 0 + + .org-card-name + flex: 1 + color: #5A2A82 + font-weight: 500 + font-size: 14px + text-decoration: none + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + .org-card-size + display: inline-flex + align-items: center + justify-content: center + min-width: 24px + height: 24px + padding: 0 6px + background: #e5e7eb + color: #1a202c + font-weight: 700 + font-size: 12px + border-radius: 4px + + html.dark & + background: rgba(90, 42, 130, 0.3) + color: #ffffff + + .org-card-body + display: grid + grid-template-columns: 1fr 1fr + gap: 8px 16px + font-size: 12px + + .org-card-row + display: flex + justify-content: space-between + align-items: center + + .org-card-label + color: #6b7280 + font-size: 11px + + html.dark & + color: #9ca3af + + .org-card-value + color: #374151 + font-weight: 500 + + html.dark & + color: #d1d5db + + // Desktop Table View + .desktop-org-table + display: none + + @media (min-width: 768px) + display: table + + .orgs-layout + display: flex + flex-direction: column + gap: 20px + align-items: flex-start + + @media (min-width: 768px) + flex-direction: row + + .left-column + width: 100% + display: flex + flex-direction: column + gap: 20px + + @media (min-width: 768px) + width: 32% + + .right-column + width: 100% + display: flex + flex-direction: column + gap: 20px + + @media (min-width: 768px) + width: 68% #newest-orgs - width: 32% - .row - margin-bottom: 15px - margin-left: 0px - padding-top: 15px - .col-md-3 - margin-left: -2px - padding-left: 0px - p - margin-left: 5px - .col-md-9 a - @include site-link-color - font-weight: bold - font-size: 15px + width: 100% + + .newest-orgs-card + background: #ffffff + border-radius: 16px + overflow: hidden + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + transition: box-shadow 0.3s ease + + html.dark & + background: #2D1548 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + &:hover + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23) + + html.dark & + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.32), 0 3px 6px rgba(0, 0, 0, 0.46) + + .card-header + padding: 16px + border-bottom: 2px solid #e2e8f0 + background: #ffffff + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + background: #2D1548 + + h2 + margin: 0 !important + padding: 0 !important + font-size: 16px !important + font-weight: 700 !important + + .card-body + padding: 16px + display: flex + flex-direction: column + gap: 12px + + .org-item + display: flex + align-items: flex-start + gap: 12px + padding: 8px 0 + + .org-logo + flex-shrink: 0 + text-decoration: none + + a + display: inline-block + line-height: 0 + + .org-info + flex: 1 + min-width: 0 + + .org-name + display: block + color: #5A2A82 + font-weight: 600 + font-size: 14px + text-decoration: none + margin-bottom: 6px + white-space: nowrap + overflow: hidden + text-overflow: ellipsis + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + .org-stats + display: flex + flex-direction: column + gap: 2px + font-size: 12px + + .projects-count + .projects-value + color: #1a202c + font-weight: 600 + text-decoration: none + + html.dark & + color: #ffffff + + &:hover + text-decoration: underline + + .added-time + color: #4b5563 + font-size: 11px + font-style: italic + + html.dark & + color: #d1d5db + + .card-footer + padding: 12px 16px + border-top: 2px solid #e2e8f0 + background: #f8f9fa + text-align: left + + html.dark & + border-top-color: rgba(90, 42, 130, 0.3) + background: #2D1548 + + .more-link + color: #5A2A82 + font-size: 12px + text-decoration: none + font-weight: 500 + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline #orgs_by_30_days_volume min-height: 260px - width: 64% - margin-left: 20px + width: 100% #most-active-orgs - width: 32% - padding: 0px 0px 0px 10px - border-radius: 0 - margin-bottom: 0px - .title - margin-top: 7px - margin-bottom: 15px - .row - margin-bottom: 15px - .col-md-3 a - margin: 5px 0px 0px 0px - .col-md-9 a - @include site-link-color - font-weight: bold - font-size: 15px - .row#active-org-timestamp - font-size: 9px - font-style: italic - margin-bottom: 0px + width: 100% + + .most-active-card + background: #ffffff + border-radius: 16px + overflow: hidden + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + transition: box-shadow 0.3s ease + + html.dark & + background: #2D1548 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + &:hover + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23) + + html.dark & + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.32), 0 3px 6px rgba(0, 0, 0, 0.46) + + .card-header + padding: 16px + border-bottom: 2px solid #e2e8f0 + background: #ffffff + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + background: #2D1548 + + h2 + margin: 0 !important + padding: 0 !important + font-size: 16px !important + font-weight: 700 !important + + .card-body + padding: 16px + display: flex + flex-direction: column + gap: 12px + + .org-item + display: flex + align-items: flex-start + gap: 12px + padding: 8px 0 + + .org-logo + flex-shrink: 0 + text-decoration: none + + a + display: inline-block + line-height: 0 + + .org-info + flex: 1 + min-width: 0 + + .org-name + display: block + color: #5A2A82 + font-weight: 600 + font-size: 14px + text-decoration: none + margin-bottom: 6px + white-space: nowrap + overflow: hidden + text-overflow: ellipsis + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + .org-stats + display: flex + align-items: center + gap: 6px + font-size: 12px + + .commits-value + color: #1a202c + font-weight: 600 + text-decoration: none + + html.dark & + color: #ffffff + + .stat-label + color: #4b5563 + font-size: 11px + margin-bottom: 0 + + html.dark & + color: #d1d5db + + .card-footer + display: flex + justify-content: space-between + align-items: center + padding: 12px 16px + border-top: 2px solid #e2e8f0 + background: #f8f9fa + font-size: 12px + + html.dark & + border-top-color: rgba(90, 42, 130, 0.3) + background: #2D1548 + + p + margin: 0 + color: #4b5563 + + html.dark & + color: #9ca3af + + .footer-text + flex: 0 0 auto + + p + font-style: italic + font-size: 11px + + .footer-timestamp + flex: 0 0 auto + text-align: right + + p + font-style: italic + font-size: 11px #stats-by-sector + overflow-x: auto + + table + font-size: 12px + + @media (min-width: 768px) + font-size: 14px + + thead th + padding: 8px 6px !important + font-size: 11px + background-color: #5b5d6237 + color: #000 + + html.dark & + background-color: #3A1D58 + color: #ffffff + + @media (min-width: 768px) + padding: 12px !important + font-size: 14px + + tbody td + padding: 8px 6px !important + + @media (min-width: 768px) + padding: 12px !important + .gauge-1 //commercial .highcharts-color-0 fill: #7b559b @@ -82,11 +616,36 @@ fill: #482268 stroke: #482268 + html.dark & + .gauge-1 // Commercial - bright yellow + .highcharts-color-0 + fill: #ffb91a !important + stroke: #ffb91a !important + + .gauge-2 // Education - lighter yellow + .highcharts-color-0 + fill: #ffd966 !important + stroke: #ffd966 !important + + .gauge-3 // Government - light golden + .highcharts-color-0 + fill: #ffd966 !important + stroke: #ffd966 !important + + .gauge-4 // Non-Profit - deeper orange-yellow + .highcharts-color-0 + fill: #e6a617 !important + stroke: #e6a617 !important + #commit-gauge - width: 220px - height: 80px + width: 120px + height: 60px margin: 0 auto + @media (min-width: 768px) + width: 220px + height: 80px + #org_filter_no_result width: 620px height: 260px @@ -98,11 +657,22 @@ line-height: 210px .progress#progress-container - width: 180px + width: 100px border: 1px solid #afd5eb border-radius: 0 + @media (min-width: 768px) + width: 180px + #progress-bar - background-color: color-gradient($SECONDARY_SLATE_BLUE, 120) + background-color: #482268 color: white padding: 0 0 0 3px + font-size: 11px + + @media (min-width: 768px) + font-size: 14px + + html.dark & + background-color: #ffb91a + color: #000 diff --git a/app/assets/stylesheets/page.sass b/app/assets/stylesheets/page.sass index cc813f4f1..c7dcdff5a 100644 --- a/app/assets/stylesheets/page.sass +++ b/app/assets/stylesheets/page.sass @@ -1,25 +1,50 @@ @import "base" +// Mobile menu slide down animation +@keyframes slideDown + 0% + opacity: 0 + transform: translateY(-10px) + 100% + opacity: 1 + transform: translateY(0) + body - background-color: #211e1e !important - font-family: Roboto - font-size: 1.3rem !important + background-color: white !important + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" + font-size: 1.3rem margin: 0 + padding: 0 color: #000 + width: 100% + overflow-x: hidden + background-color: #f9fafb !important -#page - width: 980px - background-color: white - margin: 0 auto - padding: 10px 10px 0 10px +.container#page, #page + width: 100% !important + max-width: 100% !important + background-color: #f9fafb + margin: 0 !important + padding: 0 !important + transition: background-color 0.3s ease + html.dark & + background-color: #1D0631 header - background-color: white - margin: -10px -10px 0px -10px + // Figma design: gradient background for entire header + background: linear-gradient(to right, #000000, #1D0631, #5A2A82) + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" + font-size: 16px + margin: 0 + padding: 0 + width: 100% + position: sticky + top: 0 + z-index: 1000 @media only all and (min-width: 320px) and (max-width: 480px) body - font-size: 10px !important + font-size: 13px !important overflow-x: hidden !important width: revert !important #page @@ -27,60 +52,99 @@ header overflow-x: hidden !important @media (min-width: 540px) and (max-width: 1024px) - #page - margin: 0 auto - padding: 10px 10px 0 10px - width: auto + #page, .container#page + margin: 0 !important + padding: 0 !important + width: 100% !important + max-width: 100% !important #nav-top-bar - margin: 11px 11px 0 11px + width: 100% + max-width: 1280px + margin: 0 auto + padding: 0 32px + display: flex + align-items: center .new_main_menu - list-style: none - margin: 0 - padding: 0 + list-style: none !important + margin: 0 !important + padding: 0 !important + display: flex !important + align-items: center !important + gap: 18px !important li - display: inline-block - margin-right: 32px + display: inline-block !important + margin: 0 !important + padding: 0 !important + list-style-type: none !important + &.menu_item + display: inline-block !important + margin: 0 !important + padding: 0 !important a - @include top-navbar-menu-colors - font-size: 11pt - text-decoration: none + color: white !important + font-size: 14px !important + text-decoration: none !important + font-weight: 500 !important + transition: color 0.3s ease !important + white-space: nowrap !important + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important + line-height: 1.5 !important + letter-spacing: 0 !important + padding: 6px 0 !important + margin: 0 !important + display: inline-block !important + background: none !important + border: none !important + box-shadow: none !important + &:hover, &:focus, &:active + color: #d1d5db !important + text-decoration: none !important + background: none !important + &.select + color: #FFB91A !important + font-weight: 600 !important #navbar-inner - @include top-navbar-colors - padding-top: 1px - margin-left: 4px - margin-right: 5px - height: 42px + // Navigation moved to main navbar - hide this section + display: none -@media (min-width: 320px) and (max-width: 340px) +@media (min-width: 320px) and (max-width: 480px) #nav-top-bar - margin: 11px 4px 0 0px !important + padding: 0 16px .new_main_menu + gap: 16px + .mobile-menu + .mobile-menu-content + padding: 0 8px + .mobile-menu-items li - margin-right: 2px !important a - font-size: 10px !important - -@media (min-width: 341px) and (max-width: 480px) + padding: 10px 12px + font-size: 13px + +@media (min-width: 481px) and (max-width: 768px) #nav-top-bar - margin: 11px 10px 0 11px !important - .new_main_menu - li - margin-right: 6px !important - a - font-size: 10px !important -#nav-top-bar .new_main_menu li a:hover - color: #fff - text-decoration: underline + padding: 0 24px + .new_main_menu + gap: 24px #page_contents + padding: 0 32px 24px + max-width: 100% .separator-div @include site-separator-color + + // Mobile responsive padding + @media (max-width: 767px) + padding: 0 16px 16px + #project_container background-color: white margin: 0 auto - padding: 10px 10px 0 10px + padding: 10px 20px 0 20px + width: 100% + max-width: 100% #page-contents width: auto #project_masthead @@ -92,143 +156,965 @@ header padding-left: 0 margin-left: -1rem .thirtyfive_project_activity_text - margin-top: 5.5rem line-height: 1.3rem .thirtyfive_project_activity_level_na top: 1rem .quick_reference_heading, .nutshell_heading padding-left: 2rem -@media (min-width: 320px) and (max-width: 480px) +// sm: 640px+ +@media (min-width: 640px) .navbar - margin-right: 0px !important - .btn - font-size: 10px !important - .company-div - float: none !important - .navbar_large_text - font-size: 18px !important + .navbar-container + padding: 0 16px + gap: 12px + .logo-and-brand + gap: 6px + .logo-div + .logo_img + height: 28px + .company-div + .navbar_large_text + font-size: 16px + .separator-div + display: block + height: 24px + .actions-div + gap: 8px + .theme-toggle-btn + padding: 8px + .theme-icon + font-size: 20px + .mobile-menu-toggle + padding: 8px + i + font-size: 20px + .mobile-menu + .mobile-menu-content + padding: 0 12px + .mobile-menu-items + li + a + padding: 12px 16px + +// md: 768px+ +@media (min-width: 768px) + .navbar + .navbar-container + padding: 0 24px + gap: 16px + .logo-div + .logo_img + height: 32px + .company-div + .navbar_large_text + font-size: 32px + .separator-div + display: block + height: 26px + .logo-and-brand + gap: 10px + .mobile-menu + .mobile-menu-content + padding: 0 24px + .mobile-menu-items + li + a + padding: 14px 20px + font-size: 15px + +// lg: 1024px+ (Desktop) +@media (min-width: 1024px) + .navbar + min-height: 72px + .navbar-container + padding: 0 32px + min-height: 72px + .logo-and-brand + gap: 12px + .logo-div + .logo_img + height: 32px !important + .company-div + .navbar_large_text + font-size: 20px + .separator-div + height: 28px + .actions-div + gap: 16px + .nav-menu + display: flex !important + .new_main_menu + gap: 18px !important + li + margin: 0 !important + padding: 0 !important + a + font-size: 14px !important + font-weight: 500 !important + padding: 6px 0 !important + margin: 0 !important + .desktop-actions + display: flex !important + .mobile-menu-toggle + display: none !important + .mobile-menu + display: none + +// xl: 1280px+ +@media (min-width: 1280px) + .navbar + min-height: 80px + .navbar-container + min-height: 80px + .logo-div + .logo_img + height: 48px + .company-div + .navbar_large_text + font-size: 24px + .nav-menu + .new_main_menu + gap: 28px !important + li + margin: 0 !important + padding: 0 !important + a + font-size: 16px !important + font-weight: 500 !important + padding: 6px 0 !important + margin: 0 !important + +// 2xl: 1536px+ +@media (min-width: 1536px) + .navbar + min-height: 88px + .navbar-container + min-height: 88px + .logo-div + .logo_img + height: 32px + .company-div + .navbar_large_text + font-size: 30px + .nav-menu + .new_main_menu + gap: 36px !important + li + margin: 0 !important + padding: 0 !important + a + font-size: 16px !important + font-weight: 500 !important + padding: 6px 0 !important + margin: 0 !important + +// Mobile/Tablet: < 1024px (show hamburger menu) +@media (max-width: 1023px) + .navbar + .nav-menu + display: none !important + .desktop-actions + display: none !important + .mobile-menu-toggle + display: flex !important + .actions-div .header-search-form + display: none !important .navbar + // Figma design: gradient background with purple theme + position: sticky + top: 0 + z-index: 1000 + background: linear-gradient(to right, #000000, #1D0631, #5A2A82) + backdrop-filter: blur(12px) + -webkit-backdrop-filter: blur(12px) border-width: 0 - margin-top: 15px - margin-right: 15px - margin-bottom: 5px !important - margin-left: 15px - padding: 0 0 5px 0 - min-height: 30px + border-bottom: 1px solid rgba(31, 41, 55, 0.8) + margin: 0 + padding: 0 + min-height: 64px + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2) + width: 100% + display: flex + align-items: flex-start + flex-wrap: wrap + overflow: visible + z-index: 1001 + // Override global button styles for navbar + .btn + border: none !important + border-width: 0 !important + box-shadow: none !important + &:active + top: 0 !important + left: 0 !important + .navbar-container + max-width: 100% + margin: 0 auto + padding: 0 16px + width: 100% + min-height: 64px + display: flex !important + flex-direction: row !important + flex-wrap: nowrap !important + align-items: center + justify-content: space-between + gap: 12px + position: relative + .hidden + display: none !important + position: absolute !important + width: 0 !important + height: 0 !important + overflow: hidden !important + .logo-and-brand + display: flex + align-items: center + gap: 8px + flex-shrink: 0 + min-width: 0 .logo-div - height: 35px - float: left + display: flex + align-items: center + flex-shrink: 0 .logo_link text-decoration: none - margin-top: 3px + display: flex + align-items: center .logo_img - height: 100% + height: 20px + width: auto + display: block + object-fit: contain .company-div - @include top-navigation-colors - float: left + color: white !important + display: flex + align-items: center + min-width: 0 .navbar_small_text font-size: 10px !important .navbar_large_text - font-size: 24px + color: white !important + font-weight: 700 + line-height: 1.25 + letter-spacing: 0 + white-space: nowrap a - @include top-navigation-colors + color: white !important text-decoration: none - .spacing-div - float: left - width: 2px - height: 30px - background-color: white - margin: 0 auto + &:hover + color: #d1d5db !important + transition: color 0.3s ease .separator-div - @include site-separator-color - @include site-separator-background-color - float: left + background-color: rgba(255, 255, 255, 0.2) width: 1px - height: 30px - margin: 0 2px 0 2px + height: 20px + margin: 0 + display: none + .nav-menu + display: none + position: absolute + left: 50% + transform: translateX(-50%) + .new_main_menu + list-style: none !important + margin: 0 !important + padding: 0 !important + display: flex !important + align-items: center !important + li + display: inline-block !important + margin: 0 !important + padding: 0 !important + list-style-type: none !important + &.menu_item + display: inline-block !important + margin: 0 !important + padding: 0 !important + a + color: white !important + font-size: 14px !important + text-decoration: none !important + font-weight: 500 !important + transition: all 0.3s ease !important + white-space: nowrap !important + position: relative !important + padding: 6px 0 !important + margin: 0 !important + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important + line-height: 1.5 !important + letter-spacing: 0 !important + display: inline-block !important + background: none !important + border: none !important + box-shadow: none !important + &:hover, &:focus, &:active + color: #d1d5db !important + text-decoration: none !important + background: none !important + &.select + color: #FFB91A !important + font-weight: 600 !important + &::after + content: '' !important + position: absolute !important + bottom: 0 !important + left: 0 !important + right: 0 !important + height: 2px !important + background-color: #FFB91A !important + border-radius: 2px !important .actions-div - float: right - .twitter-text - @include twitter-background-color - color: white - font-weight: bold - font-size: 0.80em + display: flex + align-items: center + gap: 12px + flex-shrink: 0 + flex-grow: 0 + .theme-toggle-btn + padding: 8px + background-color: transparent + border: none + border-radius: 8px + cursor: pointer + display: flex + align-items: center + justify-content: center + transition: all 0.3s ease + flex-shrink: 0 + &:hover + background-color: rgba(255, 255, 255, 0.1) + transform: scale(1.05) + &:active + transform: scale(0.95) + .theme-icon + font-size: 18px + color: white + transition: all 0.3s ease + .hidden + display: none + .mobile-menu-toggle + display: none + padding: 8px + background-color: transparent + border: none + border-radius: 8px + cursor: pointer + align-items: center + justify-content: center + transition: all 0.3s ease + flex-shrink: 0 + &:hover + background-color: rgba(255, 255, 255, 0.1) + transform: scale(1.05) + &:active + transform: scale(0.95) + i + font-size: 18px + color: white .btn border: 0 - display: inline-block - font-size: 15px - line-height: 25px + display: inline-flex + align-items: center + justify-content: center + font-size: 14px + line-height: 1.5 margin: 0 - padding: 1px 6px 0 7px - .follow_btn - @include follow-button-colors - display: inline-block - padding: 0 0 0 4px - height: 24px - i, p, img - color: white - display: inline-block - vertical-align: top - i - color: #555 - vertical-align: middle - &:before - padding-right: 0 - p - font-weight: normal - line-height: 24px - text-transform: none - margin: 0 - border-radius: 0 4px 4px 0 - img - margin-right: -1px + padding: 8px 16px + border-radius: 8px + font-weight: 600 + transition: all 0.3s ease + cursor: pointer + text-decoration: none + white-space: nowrap ul list-style: none - margin: 3px 0 0 0 + margin: 0 + padding: 0 + display: flex + align-items: center + gap: 12px li - float: left - padding: 0 0 0 8px + padding: 0 + list-style: none #top_nav_actions - float: right - margin-right: 5px + display: flex + align-items: center + gap: 12px + margin: 0 + padding: 0 li - padding-top: 5px + padding: 0 + list-style: none + .btn-primary, .btn-success + background-color: #FFB91A !important + color: #000 !important + border: none !important + border-width: 0 !important + font-weight: 600 !important + padding: 10px 20px !important + border-radius: 8px !important + display: inline-flex !important + align-items: center + justify-content: center + gap: 6px + transition: all 0.3s ease + font-size: 14px !important + text-decoration: none + line-height: 1.5 !important + box-shadow: 0 2px 8px rgba(255, 185, 26, 0.3) !important + &:hover + background-color: rgba(255, 185, 26, 0.9) !important + border-color: transparent !important + text-decoration: none + transform: translateY(-2px) + box-shadow: 0 4px 12px rgba(255, 185, 26, 0.4) !important + &:active + top: 0 !important + left: 0 !important + transform: translateY(0) + box-shadow: 0 2px 4px rgba(255, 185, 26, 0.3) !important + i + font-size: 14px + margin: 0 + .btn-outline + background-color: transparent !important + color: #FFB91A !important + border: 2px solid #FFB91A !important + font-weight: 600 !important + padding: 8px 18px !important + border-radius: 8px !important + display: inline-flex !important + align-items: center + justify-content: center + gap: 6px + transition: all 0.3s ease + font-size: 14px !important + text-decoration: none + line-height: 1.5 !important + box-shadow: 0 0 0 rgba(255, 185, 26, 0) !important + backdrop-filter: blur(4px) + -webkit-backdrop-filter: blur(4px) + &:hover + background-color: rgba(255, 185, 26, 0.15) !important + border-color: #FFB91A !important + text-decoration: none + transform: translateY(-2px) + box-shadow: 0 4px 12px rgba(255, 185, 26, 0.25) !important + &:active + top: 0 !important + left: 0 !important + transform: translateY(0) + box-shadow: 0 0 0 rgba(255, 185, 26, 0) !important @media (min-width: 320px) and (max-width: 340px) #top_nav_actions margin-right: 0px !important -#logged_user_menu - a - color: black !important +// Mobile Menu +.mobile-menu + display: none + width: 100% + background: linear-gradient(to right, #1D0631, #5A2A82) + backdrop-filter: blur(12px) + -webkit-backdrop-filter: blur(12px) + border-top: 1px solid rgba(255, 255, 255, 0.15) + padding: 20px 0 + position: absolute + top: 64px + left: 0 + right: 0 + z-index: 1002 + overflow: visible + box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.2) + &.show + display: block !important + visibility: visible !important + opacity: 1 !important + animation: slideDown 0.3s ease-out + .mobile-menu-content + max-width: 1280px + margin: 0 auto + padding: 0 16px + width: 100% + display: block + visibility: visible + .mobile-menu-items + list-style: none + margin: 0 + padding: 0 + display: block + visibility: visible + li + margin-bottom: 4px + display: block + visibility: visible + a + display: block !important + padding: 14px 16px + color: white !important + text-decoration: none + border-radius: 8px + transition: all 0.3s ease + font-weight: 500 + font-size: 15px + visibility: visible !important + &:hover + background-color: rgba(255, 255, 255, 0.12) + text-decoration: none + transform: translateX(4px) + &.select + color: #FFB91A !important + font-weight: 600 + background-color: rgba(255, 185, 26, 0.15) + .mobile-menu-user + list-style: none + margin: 16px 0 0 0 + padding: 16px 0 0 0 + border-top: 1px solid rgba(255, 255, 255, 0.15) + display: block + visibility: visible + li + margin-bottom: 4px + display: block + visibility: visible + a + display: block !important + padding: 14px 16px + color: white !important + text-decoration: none + border-radius: 8px + transition: all 0.3s ease + font-size: 15px + visibility: visible !important + &:hover + background-color: rgba(255, 255, 255, 0.12) + text-decoration: none + transform: translateX(4px) + .mobile-menu-signin + margin-top: 16px + padding: 16px 0 0 0 + border-top: 1px solid rgba(255, 255, 255, 0.1) + display: block + visibility: visible + .btn-mobile-signin + width: 100% + display: flex !important + align-items: center + justify-content: center + gap: 8px + padding: 14px 20px + background-color: #FFB91A !important + color: #000 !important + border: none !important + border-radius: 12px + font-weight: 600 + font-size: 15px + text-decoration: none + transition: all 0.3s ease + visibility: visible !important + margin-bottom: 12px + box-shadow: 0 2px 8px rgba(255, 185, 26, 0.3) + &:hover + background-color: rgba(255, 185, 26, 0.9) !important + text-decoration: none + transform: translateY(-2px) + box-shadow: 0 4px 12px rgba(255, 185, 26, 0.4) + &:active + transform: translateY(0) + box-shadow: 0 2px 4px rgba(255, 185, 26, 0.3) + .btn-mobile-join-now + width: 100% + display: flex !important + align-items: center + justify-content: center + gap: 8px + padding: 14px 20px + background-color: transparent !important + color: #FFB91A !important + border: 2px solid #FFB91A !important + border-radius: 12px + font-weight: 600 + font-size: 15px + text-decoration: none + transition: all 0.3s ease + visibility: visible !important + backdrop-filter: blur(4px) + -webkit-backdrop-filter: blur(4px) + &:hover + background-color: rgba(255, 185, 26, 0.15) !important + text-decoration: none + transform: translateY(-2px) + box-shadow: 0 4px 12px rgba(255, 185, 26, 0.25) + &:active + transform: translateY(0) + box-shadow: none + html.dark & + background-color: transparent !important + color: #FFB91A !important + border-color: #FFB91A !important + &:hover + background-color: rgba(255, 185, 26, 0.15) !important + + +.dropdown#logged_user_menu + position: relative + z-index: 1000 + .dropdown-toggle + color: white !important text-transform: none - text-decoration: none - margin: 0 0px 15px - padding-right: 10px - padding-top: 3px - ul - margin-top: 40px - border-radius: 0 + text-decoration: none !important + margin: 0 + padding: 10px 16px + background-color: rgba(255, 255, 255, 0.1) + border-radius: 8px + display: inline-flex !important + align-items: center + gap: 8px + font-size: 14px + font-weight: 500 + transition: all 0.3s ease + border: 1px solid transparent + cursor: pointer !important + position: relative + z-index: 1001 + pointer-events: auto !important + &:hover + background-color: rgba(255, 255, 255, 0.15) + border-color: rgba(255, 255, 255, 0.2) + text-decoration: none !important + transform: translateY(-1px) + &:active + transform: translateY(0) + i + font-size: 14px + pointer-events: none + ul.dropdown-menu + display: none + position: absolute + right: 0 + top: calc(100% + 24px) + margin-top: 8px + border-radius: 12px + border: 1px solid rgba(255, 255, 255, 0.15) + background-color: rgba(29, 6, 49, 0.97) + backdrop-filter: blur(12px) + -webkit-backdrop-filter: blur(12px) + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3), 0 4px 10px rgba(0, 0, 0, 0.2) + z-index: 1050 + min-width: 200px + padding: 8px 0 li - width: 150px + width: 100% + overflow: hidden a - color: black + color: white !important margin: 0 + padding: 12px 16px + transition: background-color 0.2s ease, color 0.2s ease + border-radius: 8px + display: block + cursor: pointer &:hover - background-color: #0082C6 !important - color: #fff !important + background-color: rgba(255, 185, 26, 0.2) !important + color: #FFB91A !important .divider width: 100% height: 1px - margin: 13px 0 + margin: 8px 0 overflow: hidden - border-bottom: 1px solid #e5e5e5 + border-bottom: 1px solid rgba(255, 255, 255, 0.15) + &.open + ul.dropdown-menu + display: block +// Override Bootstrap's default dropdown styling +.navbar .dropdown.open .dropdown-menu + display: block !important + +#top_nav_actions + position: relative + z-index: 1000 + + +// ─── Maintenance banner (read_only_mode) ───────────────────────────────────── +.maintenance-banner + border-radius: 16px + border: 1px solid rgba(251, 191, 36, 0.4) + background: linear-gradient(to bottom right, #fffbeb, #fff7ed) + box-shadow: 0 2px 8px rgba(251, 191, 36, 0.15) + overflow: hidden + margin: 8px 16px 16px + + html.dark & + background: #2D1548 + border-color: rgba(251, 191, 36, 0.3) + box-shadow: 0 2px 8px rgba(251, 191, 36, 0.1) + +.maintenance-banner-strip + height: 6px + width: 100% + background: linear-gradient(to right, #f59e0b, #f97316, #d97706) + +.maintenance-banner-inner + display: flex + gap: 16px + padding: 20px 24px + align-items: flex-start + +.maintenance-banner-icon + flex-shrink: 0 + width: 44px + height: 44px + border-radius: 16px + background: linear-gradient(to bottom right, #f59e0b, #ea580c) + display: flex + align-items: center + justify-content: center + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2) + color: #ffffff + font-size: 20px + +.maintenance-banner-content + flex: 1 + min-width: 0 + + h3 + font-weight: 700 + font-size: 15px + color: #92400e + margin: 0 0 8px + line-height: 1.3 + + html.dark & + color: #fcd34d + + p + font-size: 13px + color: rgba(120, 53, 15, 0.85) + margin: 0 0 6px + line-height: 1.6 + + html.dark & + color: rgba(253, 230, 138, 0.9) + +.maintenance-banner-footer + margin-top: 16px + +.maintenance-banner-link + display: inline-flex + align-items: center + gap: 4px + padding: 7px 16px + border-radius: 8px + background: rgba(255, 255, 255, 0.8) + border: 1px solid #fcd34d + color: #92400e + font-size: 13px + font-weight: 600 + text-decoration: none + transition: background 0.15s, color 0.15s + + &:hover + background: #ffffff + color: #92400e + text-decoration: none + + html.dark & + background: rgba(29, 6, 49, 0.4) + border-color: rgba(251, 191, 36, 0.4) + color: #fcd34d + + html.dark &:hover + background: rgba(29, 6, 49, 0.6) + color: #fcd34d + +#flash-msg + max-width: 1024px + margin-left: auto + margin-right: auto + padding: 0 16px 16px + + @media (min-width: 640px) + padding: 0 24px 16px + +#flash-msg .alert, +.modern-alert + border-radius: 12px !important + padding: 0 !important + display: block !important + overflow: hidden + background: #ffffff !important + border: 1px solid transparent + + html.dark & + background: #2D1548 !important + border-color: rgba(255, 255, 255, 0.08) !important + + .alert-accent-strip + height: 4px + width: 100% + background: linear-gradient(to right, #5A2A82, #6366f1) + + .alert-inner + display: flex + align-items: center + padding: 14px 16px + gap: 12px + + .alert-block-content + display: flex + flex-direction: column + gap: 4px + flex: 1 + min-width: 0 + + h3, h4, p + margin: 0 + line-height: 1.5 + color: #1f2937 !important + + a + color: #5A2A82 !important + &:hover + color: #4a1f6e !important + + html.dark & + h3, h4, p + color: #f3f4f6 !important + + a + color: #c084fc !important + &:hover + color: #e9d5ff !important + + .close + margin-left: auto + align-self: flex-start + flex-shrink: 0 + position: static !important + top: auto !important + right: auto !important + color: #9ca3af !important + opacity: 1 !important + font-size: 14px !important + line-height: 1 !important + background: none !important + border: none !important + padding: 0 !important + cursor: pointer + transition: color 0.15s + + &:hover + color: #374151 !important + + html.dark & + color: rgba(255, 255, 255, 0.4) !important + + html.dark &:hover + color: rgba(255, 255, 255, 0.7) !important + + .flash-alert-icon + flex-shrink: 0 + width: 32px + height: 32px + border-radius: 10px + display: flex + align-items: center + justify-content: center + font-size: 15px + + .flash-alert-content + flex: 1 + min-width: 0 + font-size: 13px + font-weight: 500 + line-height: 1.5 + +#flash-msg .alert-danger + border-color: rgba(239, 68, 68, 0.4) + box-shadow: 0 2px 8px rgba(239, 68, 68, 0.15) + + html.dark & + background: #2D1548 !important + border-color: rgba(239, 68, 68, 0.4) + box-shadow: 0 2px 12px rgba(239, 68, 68, 0.25) + + .alert-accent-strip + background: linear-gradient(to right, #ef4444, #e11d48) + + .flash-alert-icon + background: #fee2e2 + color: #dc2626 + + html.dark & + background: rgba(239, 68, 68, 0.15) + color: #f87171 + + .flash-alert-content + color: #991b1b + + html.dark & + color: #fca5a5 + +#flash-msg .alert-info, +.modern-alert.alert-info + border-color: rgba(90, 42, 130, 0.3) + box-shadow: 0 2px 8px rgba(90, 42, 130, 0.1) + + html.dark & + background: #2D1548 !important + border-color: rgba(90, 42, 130, 0.5) + + .alert-accent-strip + background: linear-gradient(to right, #5A2A82, #6366f1) + + .flash-alert-icon + background: rgba(90, 42, 130, 0.1) + color: #5A2A82 + + html.dark & + background: rgba(90, 42, 130, 0.25) + color: #e9d5ff + + .flash-alert-content + color: #3d1c5a + + html.dark & + color: #e9d5ff + +#flash-msg .alert-success + border-color: rgba(16, 185, 129, 0.35) + box-shadow: 0 2px 8px rgba(16, 185, 129, 0.12) + + html.dark & + background: #2D1548 !important + border-color: rgba(16, 185, 129, 0.35) + + .alert-accent-strip + background: linear-gradient(to right, #10b981, #0d9488) + + .flash-alert-icon + background: #d1fae5 + color: #065f46 + + html.dark & + background: rgba(16, 185, 129, 0.15) + color: #6ee7b7 + + .flash-alert-content + color: #065f46 + + html.dark & + color: #6ee7b7 + +// Constrain to form card width on auth pages +.signup-right-panel + #flash-msg + max-width: 440px + margin: 0 0 16px + padding: 0 -#flash-msg .alert - border-radius: 0 #page-contents margin-left: 5px @@ -277,7 +1163,6 @@ header @media (min-width: 320px) and (max-width: 480px) h2 - font-size: 12px !important margin-top: 5px !important .navbar .follow_btn @@ -326,7 +1211,7 @@ table box-shadow: none border-radius: 0px -.well#sign-in-text +.well#sign-in-text, .oh-card#sign-in-text @include site-well-color border: medium none box-shadow: none @@ -337,7 +1222,7 @@ table .well#new_login width: auto -.well#accounts-sign-up +.well#accounts-sign-up, .oh-card#accounts-sign-up @include site-well-color border: medium none box-shadow: none @@ -436,7 +1321,7 @@ fieldset .quick_reference_container,.nutshell_container,.project_summary_container,#vulnerability_per_version,#vulnerability-report,#did_you_know margin-bottom: 3rem word-wrap: break-word - .well + .well, .oh-card @include site-well-color height: 100% margin-bottom: 0 @@ -479,7 +1364,7 @@ fieldset .language_percentage_indicator width: 2rem .quick_reference_container - .well + .well, .oh-card .col-xs-4 padding-left: 0 @@ -533,3 +1418,189 @@ fieldset width: 14rem .language_percentage_indicator width: 3rem + +// Dark mode guards for legacy project security/no-analysis blocks +html.dark + #project_container + .project_row + #did_you_know + .well, .oh-card + background-color: #1D0631 !important + border: 1px solid #4b5563 !important + box-shadow: none !important + + h4 + color: #f8fafc !important + + p, li, span, .indent + color: #cbd5e1 !important + + a + color: #60a5fa !important + + .no_analysis_message + .alert.alert-info.alert-block + background-color: #2D1548 !important + border: 1px solid rgba(159, 122, 186, 0.35) !important + + p, + .analysis_in_progress, + .indent, + span + color: #60a5fa !important + +// Modern Info Card Styles for No Analysis Summary +.no_analysis_message + margin-bottom: 24px + + .modern-info-card + background: #ffffff + border-radius: 16px + overflow: hidden + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.08) + transition: box-shadow 0.2s ease + + .info-card-header + display: flex + align-items: center + gap: 16px + padding: 24px + + .info-icon-wrapper + width: 40px + height: 40px + flex-shrink: 0 + background: linear-gradient(135deg, #5A2A82 0%, #9F7ABA 100%) + border-radius: 10px + display: flex + align-items: center + justify-content: center + + i + color: white + font-size: 20px + + .info-content + flex: 1 + + .info-title + margin: 0 + font-size: 20px + font-weight: 600 + color: #111827 + line-height: 1.4 + + .info-card-body + padding: 0 24px 24px 24px + text-align: center + font-size: 17px + + @media (max-width: 768px) + padding: 0 24px 24px 24px + + .info-text + margin: 0 0 12px 0 + line-height: 1.6 + color: #374151 + text-align: center + + &:last-child + margin-bottom: 0 + + .info-action + margin-top: 24px + text-align: center + + .info-link + color: #0E4B7A + font-weight: 500 + text-decoration: none + transition: color 0.2s ease + + &:hover + color: #2E8B9E + text-decoration: underline + +// Dark Mode Styles for Modern Info Card +html.dark + .no_analysis_message + .modern-info-card + background-color: #2D1548 + + .info-icon-wrapper + background: linear-gradient(135deg, #5A2A82 0%, #9F7ABA 100%) + + .info-title + color: #f9fafb + + .info-text + color: #cbd5e1 + + .info-link + color: #60a5fa + + &:hover + color: #93c5fd + +body.dark + .no_analysis_message + .modern-info-card + background-color: #2D1548 + + .info-title + color: #f9fafb + + .info-text + color: #cbd5e1important + +body.dark + #project_container + .project_row + #did_you_know + .well, .oh-card + background-color: #1D0631 !important + border: 1px solid #4b5563 !important + +// Full-width layout styles +.page-content-wrapper + width: 100% + max-width: 100% + margin: 0 auto + padding: 20px + +// Ensure home page content uses full width +.home-content, .explore-content, .billboard + width: 100% + max-width: 100% + margin: 0 auto + padding: 0 20px + +// Override Bootstrap navigation styles +.navbar .nav-menu .new_main_menu li.menu_item a, +.navbar ul.new_main_menu li.menu_item a, +ul.new_main_menu li.menu_item a + color: white !important + font-size: 14px !important + font-weight: 500 !important + text-decoration: none !important + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important + line-height: 1.5 !important + padding: 6px 0 !important + margin: 0 !important + background: none !important + border: none !important + box-shadow: none !important + letter-spacing: 0 !important + &:hover, &:focus, &:active, &:visited + color: #d1d5db !important + text-decoration: none !important + background: none !important + &.select + color: #FFB91A !important + font-weight: 600 !important + +@media (min-width: 1280px) + .navbar .nav-menu .new_main_menu li.menu_item a, + .navbar ul.new_main_menu li.menu_item a, + ul.new_main_menu li.menu_item a + font-size: 16px !important diff --git a/app/assets/stylesheets/page_loader.sass b/app/assets/stylesheets/page_loader.sass new file mode 100644 index 000000000..be1cce5ff --- /dev/null +++ b/app/assets/stylesheets/page_loader.sass @@ -0,0 +1,48 @@ +// Page loader spinner styling +.page-loader + position: fixed + top: 0 + left: 0 + width: 100% + height: 100% + background: rgba(255, 255, 255, 0.95) + display: flex + align-items: center + justify-content: center + z-index: 9999 + opacity: 1 + transition: opacity 0.3s ease-out + + html.dark & + background: rgba(0, 0, 0, 0.95) + + &.hidden + display: none + + .loader-content + text-align: center + + i.fa-spinner + font-size: 48px + color: #666 + margin-bottom: 16px + display: block + animation: spin 2s linear infinite + + html.dark & + color: #9ca3af + + p + color: #666 + font-size: 16px + margin: 0 + font-weight: 500 + + html.dark & + color: #d1d5db + +@keyframes spin + 0% + transform: rotate(0deg) + 100% + transform: rotate(360deg) diff --git a/app/assets/stylesheets/passwords.sass b/app/assets/stylesheets/passwords.sass index 0942460a5..c2fa850b9 100644 --- a/app/assets/stylesheets/passwords.sass +++ b/app/assets/stylesheets/passwords.sass @@ -6,3 +6,69 @@ .bold font-weight: bold + +.alter-password-heading + font-size: 24px + font-weight: 400 + margin-bottom: 24px + a + font-size: 24px + .account-basics-title + font-size: 24px + font-weight: 700 + +.alter-password-about + clear: both + margin-top: 32px + margin-left: 0 + +#alter-password-form + .help-block + color: #595959 !important + html.dark & + color: #9ca3af !important + + html.dark & + legend + color: #e5e7eb !important + border-bottom-color: #4b5563 !important + + .form-control + width: 85% !important + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + -webkit-border-radius: 16px !important + -moz-border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + &::placeholder + color: #9ca3af !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #d1d5db !important + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + background: #ffffff !important + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &::placeholder + color: #6b7280 !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.1) !important + border-color: #6b7280 !important + &:focus, &:focus-visible + border-color: #ffb91a !important + background: #1D0631 !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + outline: none !important diff --git a/app/assets/stylesheets/people.sass b/app/assets/stylesheets/people.sass index c1414651b..479b5f0de 100644 --- a/app/assets/stylesheets/people.sass +++ b/app/assets/stylesheets/people.sass @@ -1,73 +1,20 @@ -@media only all and (min-width: 320px) and (max-width: 480px) - #people_index_page - h1 - font-size: 18px - margin-bottom: 0 - margin-top: 10px - h3 - font-size: 12px - margin-bottom: 0 - margin-top: 10px - .account_details - width: auto - .commits_summary - width: auto - .commits_count - width: auto - font-size: 15px - padding: 31px 0px 12px - line-height: 20px - .kudo_rank - font-size: 24px - padding: 0 - margin-top: 35px - .kudo_badge - margin-top: 28px !important - padding: 0 - .language_name - width: 90px !important - .unclaimed_committers_box .header_row h4 - font-size: 12px - .unclaimed_committers_box .header_row .btn-mini - font-size: 8px - .language_summary - width: 100px !important - #more_account - font-size: 10px - #more_unclaimed - font-size: 10px +// Figma-inspired section styling for unclaimed section +.unclaimed-section + margin-top: 40px + +// People index page — responsive container padding (preserves top padding from account.sass) +@media only all and (max-width: 480px) + .people-page-container + padding: 32px 12px 0 + +@media only all and (min-width: 481px) and (max-width: 767px) + .people-page-container + padding: 32px 16px 0 + @media only all and (min-width: 768px) and (max-width: 1024px) - #people_index_page - h1 - font-size: 24px - margin-bottom: 0 - margin-top: 10px - h3 - font-size: 18px - margin-bottom: 0 - margin-top: 10px - .account_details - width: 142px - margin-left: 5px - .commits_summary - width: 379px - .project_summary - padding: 0 - .commits_count - width: auto - font-size: 15px - padding: 31px 0px 12px - line-height: 20px - .language_summary - width: 100px !important - .language_name - width: 85px !important - .kudo_rank - font-size: 18px - padding: 0 - font-weight: 500 - margin-top: 30px !important - width: auto - .kudo_badge - margin-top: 28px !important - padding: 0 + .people-page-container + padding: 32px 24px 0 + +@media only all and (min-width: 1025px) + .people-page-container + padding: 32px 32px 0 diff --git a/app/assets/stylesheets/positions.sass b/app/assets/stylesheets/positions.sass index c607c8016..649f292d1 100644 --- a/app/assets/stylesheets/positions.sass +++ b/app/assets/stylesheets/positions.sass @@ -1,3 +1,40 @@ +#positions-index-page + max-width: 1024px + margin: 0 auto + padding: 0 24px 32px + + @media (max-width: 639px) + padding: 0 16px 24px + + .positions-section-header + margin: 0 0 16px + + h2 + font-size: 16px + font-weight: 600 + color: #111827 + line-height: 1.3 + margin: 0 + padding: 0 + float: none + display: flex + align-items: center + gap: 12px + white-space: nowrap + + html.dark & + color: #ffffff + + &::after + content: '' + flex: 1 + height: 1px + background: #e5e7eb + min-width: 40px + + html.dark & + background: rgba(90, 42, 130, 0.2) + .one-project-header-left margin-left: 15px margin-top: 5px @@ -24,8 +61,6 @@ right: 0px .one-project - width: 940px - background-color: #F2F2F2 margin-top: 20px margin-bottom: 10px border-radius: 5px @@ -37,6 +72,7 @@ .one-project-name margin-top: 16px + margin-left: 20px .title font-size: 1.25em font-weight: bold @@ -51,6 +87,7 @@ .one-project-contribution word-wrap: break-word + margin-left: 20px #position_form input @@ -59,9 +96,147 @@ .select-language-experiences margin-bottom: 10px - // Fixes a known problem with applying chosen on hidden elements. - .chzn-container - width: 220px !important + + // Full-width container — remove hardcoded 220px (chosen on hidden element fix + // is handled by re-init in JS when #additional-fields is shown) + .chosen-container + width: 100% !important + + // Multi-select tag input box + .chosen-container-multi .chosen-choices + width: 100% + min-height: 42px + border: 2px solid #e5e7eb !important + border-radius: 16px !important + background: #ffffff !important + background-image: none !important + padding: 6px 10px !important + box-shadow: 0 1px 2px 0 rgba(0,0,0,0.05) !important + cursor: text + transition: border-color 0.3s ease, box-shadow 0.3s ease + + &:hover + border-color: #d1d5db !important + box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06) !important + + // Active (focused) state + .chosen-container-active .chosen-choices + border-color: #5A2A82 !important + box-shadow: 0 0 0 4px rgba(14,75,122,0.1) !important + + // Selected language pills + .chosen-container-multi .chosen-choices li.search-choice + background: #f3e8ff !important + background-image: none !important + border: 1px solid #d8b4fe !important + border-radius: 20px !important + color: #5A2A82 !important + font-size: 12px + font-weight: 500 + padding: 3px 22px 3px 10px !important + box-shadow: none !important + line-height: 1.5 + + // Close (×) button on pills — use FontAwesome × + .chosen-container-multi .chosen-choices li.search-choice .search-choice-close + background: none !important + font-size: 0 + top: 50% + transform: translateY(-50%) + right: 6px + width: 14px + height: 14px + display: flex + align-items: center + justify-content: center + + &::after + content: '\00d7' + font-size: 14px + color: #7c3aed + line-height: 1 + + &:hover::after + color: #5A2A82 + + // Search input inside the tag box + .chosen-container-multi .chosen-choices li.search-field input[type="text"] + color: #374151 + font-size: 14px + height: 28px + + // Dropdown + .chosen-container .chosen-drop + border: 1px solid #e5e7eb !important + border-top: none !important + border-radius: 0 0 12px 12px !important + box-shadow: 0 4px 6px rgba(0,0,0,0.08) !important + background: #ffffff !important + z-index: 9999 + + .chosen-results + max-height: 200px + overflow-y: auto + -webkit-overflow-scrolling: touch + + .chosen-results li + font-size: 13px + color: #111827 !important + padding: 7px 12px + + &.highlighted + background: #5A2A82 !important + color: #ffffff !important + + &.result-selected + color: #9ca3af !important + + // Dark mode + html.dark & + .chosen-container-multi .chosen-choices + background: #1D0631 !important + border-color: rgba(90,42,130,0.5) !important + + &:hover + border-color: #6b7280 !important + + .chosen-container-active .chosen-choices + border-color: #ffb91a !important + box-shadow: 0 0 0 4px rgba(255,185,26,0.1) !important + + .chosen-container-multi .chosen-choices li.search-choice + background: rgba(90,42,130,0.3) !important + border-color: rgba(90,42,130,0.6) !important + color: #e9d5ff !important + + .search-choice-close::after + color: #e9d5ff + + .chosen-container-multi .chosen-choices li.search-field input[type="text"] + color: #d1d5db + + .chosen-container .chosen-drop + background: #1D0631 !important + border-color: rgba(90,42,130,0.5) !important + + .chosen-results li + color: #d1d5db !important + background: #1D0631 + + &.highlighted + background: #ffb91a !important + color: #1D0631 !important + +// Prevent border-radius and flex containers from clipping Chosen dropdowns +#position-edit-page + .oh-card, + #position_form, + fieldset.position-fields, + fieldset.position-fieldset + overflow: visible !important + +.position-date-group + overflow: visible !important #position_organization_name float: initial @@ -78,4 +253,400 @@ .position_affiliation p.error display: inline-block - margin: 5px 0 !important + +@media (max-width: 767px) + .one-project-header-left + width: 100% !important + float: none !important + margin-left: 0 !important + box-sizing: border-box + display: flex + align-items: flex-start + gap: 10px + + .logo + flex-shrink: 0 + margin: 6px 0 10px 0 !important + + .col-md-8.one-project-name + margin-left: 0 !important + margin-top: 8px !important + flex: 1 + min-width: 0 + + .title + font-size: 1em !important + word-break: break-word + + .col-md-8.one-project-contribution + margin-left: 0 !important + + .project-organization + margin-left: 0 !important + + .one-project-header-right + width: 100% !important + float: none !important + margin-right: 0 !important + margin-left: 0 !important + min-height: auto !important + position: static !important + + p + position: static !important + +@media (min-width: 768px) and (max-width: 1024px) + .one-project-header-left + width: calc(100% - 220px) !important + float: none !important + margin-left: 0 !important + box-sizing: border-box + display: flex + align-items: flex-start + gap: 10px + + .logo + flex-shrink: 0 + margin: 6px 0 10px 0 !important + + .col-md-8.one-project-name + margin-left: 0 !important + margin-top: 6px !important + float: none !important + width: auto !important + flex: 1 + min-width: 0 + + .title + word-break: break-word + + .col-md-8.one-project-contribution + margin-left: 0 !important + float: none !important + width: auto !important + + .project-organization + margin-left: 0 !important + + .one-project-header-right + width: 200px !important + margin-right: 10px !important + +// ── Position edit page ──────────────────────────────────────────────────────── +#position-edit-page + + .position-edit-title + font-size: 22px + font-weight: 400 + margin-bottom: 24px + color: #111827 + + html.dark & + color: #f3f4f6 + + a + font-size: 22px + color: #5A2A82 + text-decoration: none + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + .position-edit-title__sep + margin: 0 8px + color: #9ca3af + font-weight: 300 + + .position-edit-row + display: flex + justify-content: center + + // Card inherits .oh-card base styles; just constrain and pad it + .oh-card + padding: 28px 32px + box-sizing: border-box + +// ── Fields inside the form ──────────────────────────────────────────────────── +.position-fields + border: none + margin: 0 + padding: 0 + +// Reset fieldset used for radio/date groups (replaces div+label for a11y) +.position-fieldset + border: none + margin: 0 + padding: 0 + min-width: 0 + +.position-date-group + margin-top: 8px + display: flex + flex-wrap: wrap + gap: 8px + align-items: center + +.position-field-group + margin-bottom: 20px + + &:last-child + margin-bottom: 0 + + &.position_affiliation + .controls + display: flex + flex-direction: row + align-items: center + gap: 8px + + select.chzn-select + display: none !important + + .chosen-container + flex: 1 + min-width: 0 + width: auto !important + + .chosen-single + width: 100% + + .form-control:not(.chzn-select) + flex: 1 + min-width: 0 + width: auto !important + + .position-help + flex: 0 0 100% + margin-top: 4px + +.position-label + display: block + font-size: 13px + font-weight: 600 + color: #374151 + margin-bottom: 6px + + html.dark & + color: #d1d5db + + &.required::after + content: ' *' + color: #ef4444 + +.position-help + font-size: 12px + color: #6b7280 + margin-top: 5px + margin-bottom: 0 + + html.dark & + color: #9ca3af + +// form-control overrides for edit page — matches project basics style +#position_form + .form-control + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + -webkit-border-radius: 16px !important + -moz-border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + width: 100% !important + font-size: 14px + transition: border-color 0.3s ease, box-shadow 0.3s ease + + &::placeholder + color: #9ca3af !important + opacity: 1 + + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #d1d5db !important + + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + background: #ffffff !important + + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + + &::placeholder + color: #6b7280 !important + opacity: 1 + + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #6b7280 !important + + &:focus, &:focus-visible + border-color: #ffb91a !important + background: #1D0631 !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + outline: none !important + + .position-textarea + resize: vertical + line-height: 1.6 + +// Radio button rows +.position-radio-group + display: flex + flex-wrap: wrap + gap: 8px 20px + margin-bottom: 10px + +.position-radio-label + display: inline-flex + align-items: center + gap: 6px + font-size: 14px + color: #374151 + cursor: pointer + font-weight: 400 + margin: 0 + + html.dark & + color: #d1d5db + + input[type="radio"] + margin: 0 + accent-color: #5A2A82 + +// Expand toggle link +.position-expand-toggle + margin-bottom: 12px + + a + font-size: 14px + color: #5A2A82 + text-decoration: none + display: inline-flex + align-items: center + gap: 6px + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + .position-expand-icon + font-size: 12px + +// Project experience rows +.position-project-exp + display: flex + flex-wrap: wrap + align-items: flex-start + gap: 8px + margin-bottom: 12px + + .form-control + flex: 1 + min-width: 160px + + .btn.remove + flex-shrink: 0 + +// Actions bar +.position-actions + display: flex + align-items: center + gap: 12px + padding-top: 20px + border-top: 1px solid #f3f4f6 + margin-top: 8px + + html.dark & + border-top-color: rgba(90,42,130,0.2) + +.position-btn-submit, +.position-btn-delete + display: inline-flex + align-items: center + justify-content: center + height: 40px + min-width: 140px + box-sizing: border-box + padding: 0 24px + border-radius: 8px + font-size: 14px + font-weight: 600 + line-height: 1 + cursor: pointer + text-decoration: none + +.position-btn-submit + background: linear-gradient(135deg, #5A2A82, #7B3FB5) + border: none + color: #ffffff + + &:hover, &:focus + opacity: 0.9 + color: #ffffff + +.position-btn-delete + background-image: none + border-color: currentColor + +// ── Responsive ──────────────────────────────────────────────────────────────── +@media (max-width: 767px) + #position-edit-page + padding: 16px 8px + + .position-edit-title + font-size: 18px !important + margin-bottom: 16px + + .oh-card + padding: 18px 14px !important + margin-left: 0 !important + margin-right: 0 !important + width: 100% !important + + .position-radio-group + flex-direction: column + gap: 6px + + .position-date-group + flex-direction: column + gap: 4px + + select + width: 100% !important + box-sizing: border-box + + .position-project-exp + flex-direction: column + + .form-control + width: 100% + + .position-actions + flex-direction: column + align-items: stretch + + .position-btn-submit, + .position-btn-delete + width: 100% + text-align: center + justify-content: center + +@media (min-width: 768px) and (max-width: 1024px) + #position-edit-page + padding: 20px 12px + + .oh-card + padding: 24px 20px !important + width: 90% !important diff --git a/app/assets/stylesheets/privacy.sass b/app/assets/stylesheets/privacy.sass index 17e90d5f2..bab66bf2a 100644 --- a/app/assets/stylesheets/privacy.sass +++ b/app/assets/stylesheets/privacy.sass @@ -1,40 +1,88 @@ #privacy_edit_page .admin_email_notice - :margin-left 15px - :margin-top -10px + margin-left: 15px + margin-top: -10px .email_opt_status - :line-height 25px - .email_opt_status span - :margin-left 20px + line-height: 25px #account_email_master_false width: 24px height: 18px #account_email_master_true width: 24px height: 18px - span + label position: relative top: -4px + font-weight: normal + margin-bottom: 0 .email_opted_out - :color #900D16 - :font-weight bold + color: #900D16 + font-weight: bold + html.dark & + color: #f87171 .email_opted_in - :color #0F781C - :font-weight bold + color: #0F781C + font-weight: bold + html.dark & + color: #4ade80 .select_box - :margin-top 8px - :width 80px + margin-top: 8px + width: 80px #account_email_posts_chzn, #account_email_kudos_chzn a - :height 30px - :background-color #fff + height: 30px + background-color: #fff #email_status li list-style-type: none + + html.dark & + legend + color: #e5e7eb !important + border-bottom-color: #4b5563 !important + h4 + color: #e5e7eb + p + color: #d1d5db + label + color: #d1d5db + span + color: #d1d5db + .help-inline + color: #9ca3af + .select_box + background: #374151 + color: #e5e7eb + border: 1px solid #5A2A82 + border-radius: 4px + +@media (max-width: 767px) + #privacy_edit_page + .col-md-5 + padding-left: 0 !important + padding-right: 0 !important + + span.oh-card.col-md-10 + margin-left: 15 !important + width: 90% !important + box-sizing: border-box !important + display: block !important + + .table + width: 100% !important + font-size: 13px !important + + td + display: block !important + width: 100% !important + padding: 6px 8px !important + + td[style] + width: auto !important diff --git a/app/assets/stylesheets/project_licenses.sass b/app/assets/stylesheets/project_licenses.sass new file mode 100644 index 000000000..0e6dce49e --- /dev/null +++ b/app/assets/stylesheets/project_licenses.sass @@ -0,0 +1,326 @@ +// Styles for app/views/project_licenses/ + +// Legacy form styles (kept for backward compatibility) +.license-form + legend + color: #111827 !important + font-weight: 600 + html.dark & + color: #f3f4f6 !important + + .control-label + color: #374151 !important + html.dark & + color: #d1d5db !important + + .help-block + color: #374151 !important + html.dark & + color: #d1d5db !important + + #value_select + .chosen-container-single .chosen-single + border: 2px solid #e5e7eb !important + border-radius: 16px !important + -webkit-border-radius: 16px !important + background: #ffffff !important + background-image: none !important + height: auto !important + padding: 10px 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + color: #111827 !important + line-height: 1.5 !important + width: 100% !important + &:hover + border-color: #d1d5db !important + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + .chosen-container-active > .chosen-single, + .chosen-container-active.chosen-with-drop .chosen-single + border-color: #5A2A82 !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + .chosen-container .chosen-drop + border-radius: 0 0 16px 16px !important + border-left: 2px solid #5A2A82 !important + border-right: 2px solid #5A2A82 !important + border-bottom: 2px solid #5A2A82 !important + border-top: none !important + .chosen-container + width: 100% !important + html.dark & + .chosen-container-single .chosen-single + background: #1D0631 !important + border-color: #5A2A82 !important + color: #ffffff !important + &:hover + border-color: #6b7280 !important + .chosen-container-active > .chosen-single, + .chosen-container-active.chosen-with-drop .chosen-single + border-color: #ffb91a !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + .chosen-container .chosen-drop + border-left: 2px solid #ffb91a !important + border-right: 2px solid #ffb91a !important + border-bottom: 2px solid #ffb91a !important + + .actions + display: flex + gap: 8px + align-items: center + +// New declared license page +#project-license-new-page + padding: 24px 0 + + .license-new-card-header + display: flex + align-items: center + padding: 20px 24px 16px + border-bottom: 1px solid #e5e7eb + margin-bottom: 24px + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + + .license-new-card-title + font-size: 18px + font-weight: 600 + color: #111827 + margin: 0 + display: flex + align-items: center + gap: 10px + .fa + color: #5A2A82 + font-size: 16px + html.dark & + color: #f3f4f6 + .fa + color: #ffb91a + + .license-new-form + padding: 0 24px 24px + + .license-alert + display: flex + align-items: center + gap: 8px + margin: 0 24px 20px + border-radius: 8px + padding: 12px 16px + background: #fef2f2 + border: 1px solid #fecaca + color: #b91c1c + .fa + font-size: 16px + html.dark & + background: rgba(185, 28, 28, 0.1) + border-color: rgba(185, 28, 28, 0.3) + color: #fca5a5 + + .license-field-group + margin-bottom: 20px + + .license-field-label + display: block + font-size: 14px + font-weight: 600 + color: #374151 + margin-bottom: 8px + &.required::after + content: ' *' + color: #dc2626 + html.dark & + color: #d1d5db + + .license-select-wrap + margin-bottom: 8px + .chosen-container + width: 100% !important + .chosen-container-single .chosen-single + border: 1.5px solid #d1d5db + border-radius: 8px + background: #ffffff + background-image: none !important + height: auto + padding: 10px 14px + color: #111827 + font-size: 14px + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) + line-height: 1.5 + &:hover + border-color: #9ca3af + span + display: block + overflow: hidden + text-overflow: ellipsis + white-space: nowrap + line-height: 1.5 + margin-right: 26px + // Higher specificity (0,3,2) beats retina media query (0,2,2) to prevent double arrow on HiDPI + .chosen-container-single .chosen-single div b + background: none !important + background-image: none !important + margin-top: 10px + .chosen-container-active.chosen-with-drop .chosen-single, + .chosen-container-active > .chosen-single + border-color: #5A2A82 + box-shadow: 0 0 0 3px rgba(90, 42, 130, 0.15) + .chosen-container .chosen-drop + border: 1.5px solid #5A2A82 + border-top: none + border-radius: 0 0 8px 8px + html.dark & + .chosen-container-single .chosen-single + background: #1D0631 !important + border-color: #5A2A82 + color: #f3f4f6 + .chosen-container-active.chosen-with-drop .chosen-single, + .chosen-container-active > .chosen-single + border-color: #ffb91a + box-shadow: 0 0 0 3px rgba(255, 185, 26, 0.15) + .chosen-container .chosen-drop + border-color: #ffb91a + + .license-help-text + font-size: 13px + color: #6b7280 + margin-top: 6px + margin-bottom: 0 + a + color: #5A2A82 + text-decoration: none + &:hover + text-decoration: underline + html.dark & + color: #9ca3af + a + color: #ffb91a + + .license-form-actions + display: flex + align-items: center + gap: 12px + padding-top: 8px + border-top: 1px solid #f3f4f6 + margin-top: 24px + html.dark & + border-top-color: rgba(90, 42, 130, 0.2) + + .btn-add-license + display: inline-flex + align-items: center + gap: 6px + padding: 9px 20px + font-weight: 600 + border-radius: 8px + .fa + font-size: 13px + + .btn-cancel-license + color: #6b7280 + border-color: #d1d5db + border-radius: 8px + padding: 9px 18px + &:hover + background: #f9fafb + color: #374151 + html.dark & + color: #9ca3af + border-color: #4b5563 + &:hover + background: rgba(255, 255, 255, 0.05) + color: #d1d5db + +// Declared licenses index page +#project-license-index-page + padding: 24px 0 + + .license-index-card-header + display: flex + align-items: center + justify-content: space-between + padding: 20px 24px 16px + border-bottom: 1px solid #e5e7eb + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + + .license-index-card-title + font-size: 18px + font-weight: 600 + color: #111827 + margin: 0 + display: flex + align-items: center + gap: 10px + .fa + color: #5A2A82 + font-size: 16px + html.dark & + color: #f3f4f6 + .fa + color: #ffb91a + + .license-list + padding: 8px 0 + + .license-list-item + display: flex + align-items: center + justify-content: space-between + padding: 12px 24px + border-bottom: 1px solid #f3f4f6 + &:last-child + border-bottom: none + html.dark & + border-bottom-color: rgba(255, 255, 255, 0.06) + + .license-name + display: flex + align-items: center + gap: 10px + font-size: 16px + color: #111827 + font-weight: 500 + .fa + color: #6b7280 + font-size: 14px + html.dark & + color: #f3f4f6 + + .license-actions + flex-shrink: 0 + + .btn-remove-license + display: inline-flex + align-items: center + gap: 5px + border-radius: 6px + .fa + font-size: 12px + + .license-empty-state + display: flex + flex-direction: column + align-items: center + padding: 48px 24px + text-align: center + .license-empty-icon + font-size: 48px + color: #d1d5db + margin-bottom: 16px + .license-empty-text + font-size: 15px + color: #6b7280 + margin: 0 + html.dark & + .license-empty-icon + color: #4b5563 + .license-empty-text + color: #9ca3af + + .new-license + display: inline-flex + align-items: center + border-radius: 8px + padding: 8px 16px + font-weight: 600 + font-size: 14px diff --git a/app/assets/stylesheets/project_show_redesign.sass b/app/assets/stylesheets/project_show_redesign.sass new file mode 100644 index 000000000..2d2062b61 --- /dev/null +++ b/app/assets/stylesheets/project_show_redesign.sass @@ -0,0 +1,3278 @@ +// Project Show Page Redesign - Clean Implementation Based on Figma Design +// ============================================================================ + +// Design System Variables +// ============================================================================ +$purple-dark: #1D0631 +$purple-primary: #5A2A82 +$purple-light: #9F7ABA +$yellow-accent: #FFB91A +$yellow-hover: #FFCC4D + +// Neutrals +$gray-50: #f9fafb +$gray-100: #f3f4f6 +$gray-200: #e5e7eb +$gray-300: #d1d5db +$gray-400: #9ca3af +$gray-500: #6b7280 +$gray-600: #4b5563 +$gray-700: #374151 +$gray-900: #111827 + +// Shadows +$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05) +$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) +$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) + +// System Font Stack +$font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif + +// ============================================================================ +// Global Overrides - Remove Bootstrap padding/margins +// ============================================================================ + +// Remove navbar margin globally with maximum specificity +.navbar, +.navbar-default, +.navbar-static-top, +nav.navbar, +header .navbar, +.navbar-fixed-top, +.navbar-fixed-bottom + margin-bottom: 0 !important + margin-top: 0 !important + +// Additional Bootstrap navbar overrides +body .navbar + margin-bottom: 0 !important + +// Target specific header navbar +header + .navbar + margin-bottom: 0 !important + +// Remove container and page padding when showing redesigned project page +body:has(.project-show-redesign) + #project_container, + .container-fluid#project_container + padding: 0 !important + margin: 0 !important + max-width: 100% !important + + #page-contents, + .row#page-contents + margin: 0 !important + padding: 0 !important + + > div + padding: 0 !important + +// Direct child approach - more specific +#project_container + > .row#page-contents + > .col-xs-12, + > .col-sm-12, + > .col-md-12 + padding: 0 !important + + > .project-show-redesign + margin: 0 !important + +// Force remove padding from all Bootstrap column classes in project container +.project-show-redesign + ~ * + .container, + .container-fluid, + .row, + [class*="col-"] + padding-left: 0 !important + padding-right: 0 !important + +// ============================================================================ +// Main Container +// ============================================================================ +.project-show-redesign + width: 100% + min-height: 100vh + font-family: $font-family + -webkit-font-smoothing: antialiased + -moz-osx-font-smoothing: grayscale + margin: 0 !important + + // Hide old project header elements + #project_masthead, + #project_header, + #project_icon, + #widgets, + #project_header_activity_indicator + display: none !important + + // Reset global styles that interfere + * + box-sizing: border-box + + // Override any legacy project container color styles + color: $gray-900 + + a + color: $purple-primary + + &:hover + color: $purple-primary + + // ============================================================================ + // Main Content Area + // ============================================================================ + .project-content + margin: 0 auto + padding: 16px + + @media (min-width: 768px) + padding: 24px + + @media (min-width: 1024px) + padding: 32px 0 + + // ============================================================================ + // Stats Grid (3 columns) + // ============================================================================ + .stats-grid + display: grid + grid-template-columns: repeat(3, 1fr) + gap: 8px + margin-bottom: 32px + + @media (min-width: 640px) + gap: 16px + + @media (min-width: 768px) + gap: 24px + + .stat-card + background: white + border-radius: 16px + padding: 10px 8px + text-align: center + // Material Design elevation shadow — Figma parity + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + transition: box-shadow 0.2s + + @media (min-width: 640px) + padding: 16px 12px + + @media (min-width: 768px) + padding: 24px + + &:hover + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23) + + .stat-value + font-size: 20px + font-weight: 700 + color: $purple-primary + line-height: 1 + margin-bottom: 4px + + @media (min-width: 640px) + font-size: 28px + margin-bottom: 6px + + @media (min-width: 768px) + font-size: 36px + margin-bottom: 8px + + .stat-label + font-size: 11px + color: $gray-600 + font-weight: 500 + line-height: 1.2 + + @media (min-width: 640px) + font-size: 13px + + @media (min-width: 768px) + font-size: 14px + + // ============================================================================ + // Card Components + // ============================================================================ + .gradient-card + background: white + border-radius: 16px + // Material Design elevation shadow — Figma parity + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + overflow: hidden + transition: box-shadow 0.2s + + &:hover + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23) + + .card-content + padding: 20px + + @media (min-width: 768px) + padding: 24px + + h3 + font-size: 20px !important + font-weight: 600 !important + color: $gray-900 !important + line-height: 1.3 + margin: 0 0 16px !important + padding-bottom: 12px + border-bottom: 1px solid $gray-200 + + h4 + font-size: 16px !important + font-weight: 600 !important + color: $gray-900 !important + line-height: 1.4 + margin: 0 0 12px !important + + p + font-size: 14px !important + line-height: 1.5 + color: $gray-700 !important + margin: 0 0 16px !important + + &:last-child + margin-bottom: 0 + + &.note + font-size: 12px !important + color: $gray-500 !important + font-style: italic + + ul + list-style: none + padding: 0 + margin: 0 + + // ============================================================================ + // Desktop: 3-column grid + // ============================================================================ + .three-col-grid + display: none + + @media (min-width: 1024px) + display: grid + grid-template-columns: repeat(3, 1fr) + gap: 24px + margin-bottom: 32px + + // ============================================================================ + // Mobile: Accordion cards + // ============================================================================ + .mobile-accordion + display: flex + flex-direction: column + gap: 16px + margin-bottom: 32px + + @media (min-width: 1024px) + display: none + + .accordion-card + .accordion-header + width: 100% + display: flex + align-items: center + justify-content: space-between + padding: 14px 16px + background: none + border: none + cursor: pointer + text-align: left + + @media (min-width: 640px) + padding: 20px + + .header-left + display: flex + align-items: center + gap: 12px + + svg + width: 20px + height: 20px + color: $purple-primary + + h3 + font-size: 16px !important + font-weight: 600 !important + color: $gray-900 !important + margin: 0 !important + + .chevron + width: 20px + height: 20px + color: $gray-600 + transition: transform 0.2s + + &.expanded + transform: rotate(180deg) + + .accordion-content + padding: 0 20px 20px + display: none + + &.expanded + display: block + + // ============================================================================ + // Tags + // ============================================================================ + #project_tags, + #project_tags_mobile + margin-top: 24px + + h4 + font-size: 16px !important + font-weight: 600 !important + color: $gray-900 !important + margin-bottom: 12px + display: flex + align-items: center + gap: 8px + + svg + width: 16px + height: 16px + color: $gray-900 !important + + .tags-container + display: flex + flex-wrap: wrap + gap: 8px + + .tag + padding: 4px 12px + background: $gray-100 + color: $gray-900 !important + font-size: 14px + border-radius: 6px + text-decoration: none + transition: all 0.15s + + &:hover + background: $purple-primary + color: white !important + + &.tag-empty + cursor: default + &:hover + background: $gray-100 + color: $gray-900 !important + + // ============================================================================ + // Nutshell List + // ============================================================================ + .nutshell-list + list-style: none + padding: 0 + margin: 0 + display: flex + flex-direction: column + gap: 12px + + li + display: flex + gap: 12px + padding-top: 12px + font-size: 14px !important + line-height: 1.5 + border-top: 1px solid $gray-100 + color: $gray-700 !important + + &:first-child + border-top: none + padding-top: 0 + + svg + width: 16px + height: 16px + color: $purple-primary !important + flex-shrink: 0 + margin-top: 2px + + .content + color: $gray-700 !important + flex: 1 + + a + color: $purple-primary !important + font-weight: 600 + text-decoration: none + + &:hover + text-decoration: underline + color: $purple-primary !important + + .font-semibold, + span.font-semibold + font-weight: 600 + color: $gray-700 !important + + // ============================================================================ + // Quick Reference List + // ============================================================================ + .quick-ref-list + list-style: none + padding: 0 + margin: 0 + display: flex + flex-direction: column + gap: 12px + + li + display: flex + justify-content: space-between + align-items: flex-start + gap: 16px + padding-bottom: 12px + font-size: 14px !important + border-bottom: 1px solid $gray-100 + + &:last-child + border-bottom: none + padding-bottom: 0 + + .label + color: $gray-600 !important + font-weight: 500 !important + font-size: 14px !important + flex-shrink: 0 + line-height: 1.5 + + .value + color: $purple-primary !important + font-weight: 600 !important + font-size: 14px !important + text-align: right + flex: 1 + line-height: 1.5 + + a + color: inherit !important + text-decoration: none + display: inline-flex + align-items: center + gap: 4px + font-size: 14px !important + font-weight: 600 !important + + &:hover + text-decoration: underline + color: inherit !important + + svg + width: 12px + height: 12px + flex-shrink: 0 + color: currentColor !important + + i + font-size: 14px !important + color: $purple-primary !important + font-weight: 600 !important + + // Similar projects list inside .value + .similar-projects-list + display: flex + flex-direction: column + gap: 4px + width: 100% + text-align: right + + .similar-proj-name + display: block + color: $purple-primary !important + font-size: 13px !important + font-weight: 600 !important + text-decoration: none + line-height: 1.4 + + &:hover + text-decoration: underline + + // Maximum specificity overrides for Quick Reference to prevent legacy interference + #page #project_container .project-show-redesign + .gradient-card .card-content h3 + font-size: 20px !important + font-weight: 600 !important + color: $gray-900 !important + + .quick-ref-list li + font-size: 14px !important + + .label + font-size: 14px !important + font-weight: 500 !important + color: $gray-600 !important + + .value + font-size: 14px !important + font-weight: 600 !important + color: $purple-primary !important + + a + font-size: 14px !important + font-weight: 600 !important + color: $purple-primary !important + + i + font-size: 14px !important + font-weight: 600 !important + color: $purple-primary !important + + .mobile-accordion .accordion-card .accordion-header .header-left h3 + font-size: 16px !important + font-weight: 600 !important + + // Languages section override + .languages-content .language-legend .language-item + font-size: 12px !important + + .language-name + color: $gray-700 !important + font-size: 12px !important + + a + color: $purple-primary !important + font-size: 12px !important + + .language-percent + color: $gray-500 !important + font-size: 12px !important + + // Summary sections override + .summary-section + .summary-header + font-size: 12px !important + font-weight: 600 !important + color: $gray-900 !important + + .summary-timespan + font-size: 12px !important + color: $gray-400 !important + + .summary-stat + font-size: 12px !important + color: $gray-700 !important + + .stat-number + font-weight: 600 !important + color: $gray-900 !important + + .summary-change + font-size: 12px !important + + &.positive + color: #16a34a !important + + &.negative + color: #dc2626 !important + + // ============================================================================ + // Even more specific overrides for Activity card summary sections + // ============================================================================ + #page #project_container .project-show-redesign .analysis-grid .gradient-card.analysis-card .card-content + .summary-grid .summary-section + h5.summary-header + font-size: 12px !important + font-weight: 600 !important + color: #111827 !important + margin-bottom: 4px !important + + p.summary-timespan + font-size: 12px !important + color: #9ca3af !important + margin-bottom: 8px !important + + p.summary-stat + font-size: 12px !important + color: #374151 !important + margin-bottom: 4px !important + + span.stat-number + font-weight: 600 !important + color: #111827 !important + + p.summary-change + font-size: 12px !important + + &.positive + color: #16a34a !important + + &.negative + color: #dc2626 !important + + // ============================================================================ + // Licenses Section + // ============================================================================ + .licenses-section + margin-bottom: 32px + + @media (min-width: 768px) + margin-bottom: 48px + + .section-title + display: block + visibility: visible + opacity: 1 + font-size: 20px + font-weight: 600 + color: $gray-900 + margin-bottom: 12px + line-height: 1.3 + + @media (min-width: 768px) + font-size: 24px + margin-bottom: 16px + + .license-card + margin-bottom: 16px + border-radius: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + + &:hover + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + + &:last-child + margin-bottom: 0 + + .license-header + width: 100% + display: flex + align-items: center + justify-content: space-between + padding: 16px + background: none + border: none + cursor: pointer + text-align: left + + @media (min-width: 768px) + padding: 24px + + @media (min-width: 1024px) + cursor: default + + .header-left + display: flex + align-items: center + gap: 8px + + svg + width: 20px + height: 20px + color: $purple-primary + flex-shrink: 0 + + @media (min-width: 768px) + width: 20px + height: 20px + + h4 + font-size: 16px + font-weight: 600 + color: $purple-primary + margin: 0 + line-height: 1.3 + + @media (min-width: 768px) + font-size: 18px + + .chevron + width: 20px + height: 20px + color: $gray-500 + transition: transform 0.2s + flex-shrink: 0 + + @media (min-width: 1024px) + display: none + + &.expanded + transform: rotate(180deg) + + .license-content + display: none + border-top: 1px solid $gray-200 + padding: 16px + + &.expanded + display: block + + @media (min-width: 768px) + padding: 24px + + @media (min-width: 1024px) + display: block + + .license-details-grid + display: grid + gap: 16px + + @media (min-width: 768px) + grid-template-columns: repeat(3, 1fr) + gap: 24px + + .license-detail-card + border-radius: 8px + padding: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + + @media (min-width: 768px) + padding: 20px + + &.permitted + background: linear-gradient(to bottom right, #faf5ff 0%, #f3e8ff 100%) + + h5 + color: $purple-primary + + svg + color: $purple-primary + + li + color: $gray-700 + + svg + color: $purple-primary + + &.forbidden + background: linear-gradient(to bottom right, #fff7ed 0%, #fed7aa 100%) + + h5 + color: #c2410c + + svg + color: #c2410c + + li + color: $gray-700 + + svg + color: #c2410c + + &.required + background: linear-gradient(to bottom right, #fefce8 0%, #fef3c7 100%) + + h5 + color: #a16207 + + svg + color: #a16207 + + li + color: $gray-700 + + svg + color: #a16207 + + h5 + font-size: 14px + font-weight: 600 + margin-bottom: 12px + display: flex + align-items: center + gap: 8px + + svg + width: 16px + height: 16px + flex-shrink: 0 + + ul + list-style: none + padding: 0 + margin: 0 + + li + font-size: 14px + margin-bottom: 8px + display: flex + align-items: center + gap: 8px + line-height: 1.4 + + &:last-child + margin-bottom: 0 + + svg + width: 12px + height: 12px + flex-shrink: 0 + margin-top: 0 + + .license-disclaimer + margin-top: 20px + padding-top: 16px + border-top: 1px solid $gray-200 + text-align: center + + @media (min-width: 768px) + margin-top: 24px + + p + font-size: 12px + color: $gray-500 + font-style: italic + margin: 0 + line-height: 1.5 + + .view-all-link + margin-top: 16px + text-align: right + + a + font-size: 14px !important + font-weight: 500 !important + color: $purple-primary !important + text-decoration: none + + &:visited, + &:active + color: $purple-primary !important + + &:hover + text-decoration: underline + color: $purple-primary !important + + // ============================================================================ + // Project Security Section + // ============================================================================ + .security-section + margin-bottom: 32px + + @media (min-width: 768px) + margin-bottom: 48px + + .section-title + font-size: 20px !important + font-weight: 600 !important + color: $gray-900 !important + margin-bottom: 12px !important + + @media (min-width: 768px) + font-size: 24px !important + margin-bottom: 16px !important + + .security-grid + display: grid + grid-template-columns: 1fr + gap: 16px + margin-bottom: 16px + + @media (min-width: 768px) + gap: 24px + + @media (min-width: 1024px) + grid-template-columns: repeat(3, 1fr) + + .card-subtitle + display: block !important + visibility: visible !important + opacity: 1 !important + font-size: 14px !important + font-weight: 600 !important + color: $gray-900 !important + margin-bottom: 16px !important + line-height: 1.4 + + @media (min-width: 768px) + font-size: 16px !important + + .security-link + color: $gray-900 !important + text-decoration: none + + &:hover + color: $purple-primary !important + text-decoration: underline + + .subtitle-detail + visibility: visible !important + opacity: 1 !important + display: block !important + font-size: 12px !important + font-weight: 400 !important + color: #6b7280 !important + margin-top: 4px + + @media (min-width: 768px) + display: inline !important + margin-left: 8px + + #vulnerability_version_chart + .chart-container + min-height: 250px + background: $gray-50 + border-radius: 8px + padding: 16px + + .no-data + text-align: center + padding: 60px 20px + color: $gray-600 + font-size: 14px + + // Vulnerability Report Indices + .vulnerability-indices + display: flex + flex-direction: column + gap: 24px + margin-top: 16px + + .index-section + .index-title + display: block !important + visibility: visible !important + opacity: 1 !important + font-size: 14px !important + font-weight: 500 !important + color: #4b5563 !important + margin-bottom: 8px !important + line-height: 1.4 + + .gradient-bar-container + position: relative + height: 32px + margin-bottom: 8px + + @media (min-width: 768px) + height: 40px + + .gradient-bar + width: 100% + height: 100% + background: linear-gradient(to right, #5A2A82 0%, #ffb91a 50%, #f59e0b 100%) + border-radius: 8px + + .indicator-pointer + position: absolute + top: 0 + width: 4px + height: 100% + background: #1D0631 + border-radius: 9999px + cursor: pointer + transition: all 0.2s + + &:hover + background: $gray-900 + + .index-labels + display: flex + justify-content: space-between + font-size: 12px + color: #6b7280 + + .label-left, + .label-right + max-width: 45% + + .report-link-container + margin-top: 16px + text-align: center + + .report-link + font-size: 14px !important + font-weight: 600 !important + color: $purple-primary !important + text-decoration: none + + &:hover + text-decoration: underline + + // Did You Know Card + .did-you-know-card + background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%) + border-radius: 12px + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04) + transition: box-shadow 0.2s + + &:hover + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0, 0, 0, 0.06) + + &.single + max-width: 600px + margin: 0 auto + + .card-content + padding: 20px + + @media (min-width: 768px) + padding: 24px + + .did-you-know-title + display: flex !important + visibility: visible !important + opacity: 1 !important + font-size: 16px !important + font-weight: 600 !important + color: #78350f !important + margin-bottom: 12px !important + align-items: center + gap: 8px + + @media (min-width: 768px) + font-size: 18px !important + + svg + width: 20px !important + height: 20px !important + color: #78350f !important + + .feature-list + list-style: disc + padding-left: 24px + margin: 0 + display: flex + flex-direction: column + gap: 8px + + li + visibility: visible !important + opacity: 1 !important + font-size: 14px !important + color: #78350f !important + line-height: 1.5 + + .security-footer-link + margin-top: 16px + text-align: right + + a + font-size: 14px !important + font-weight: 600 !important + color: $purple-primary !important + text-decoration: none + + &:hover + text-decoration: underline + + .no-data + text-align: center + padding: 40px 20px + color: $gray-600 + font-size: 14px + + // ============================================================================ + // No Analysis Message (Light Mode) + // ============================================================================ + .no_analysis_message + .btn.btn-primary, + .btn.btn-large.btn-primary + background-color: $purple-primary !important + border-color: $purple-primary !important + color: white !important + + &:hover, + &:focus, + &:active + background-color: $purple-dark !important + border-color: $purple-dark !important + color: white !important + +// ============================================================================ +// Dark Theme Overrides (Project Show Redesign) +// ============================================================================ +html.dark + .project-show-redesign + color: #e2e8f0 !important + + // Override any legacy color styles + a + color: $yellow-accent !important + + &:hover + color: $yellow-accent !important + + .project-content + background: transparent + + .stats-grid + .stat-card + background: #2D1548 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + .stat-value + color: $yellow-accent + + .stat-label + color: #94a3b8 + + .gradient-card + background: #2D1548 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + .card-content + h3 + color: white !important + border-bottom-color: rgba(90, 42, 130, 0.3) + + h4 + color: white !important + + p + color: #cbd5e1 !important + + &.note + color: #94a3b8 !important + + a + color: $yellow-accent !important + + &:hover + text-decoration: underline + color: $yellow-accent !important + + .tags-container + .tag + color: $gray-300 !important + background: $purple-dark + + &:hover + color: $purple-dark !important + background: $yellow-accent + + .mobile-accordion + .accordion-card + .accordion-header + .header-left + svg + color: $yellow-accent + + h3 + color: #f8fafc !important + + .chevron + color: #94a3b8 + + #project_tags, + #project_tags_mobile + h4 + color: white !important + + svg + color: white !important + + .tags-container + .tag + background: $purple-dark + color: white !important + + &:hover + background: $yellow-accent + color: $purple-dark !important + + &.tag-empty + cursor: default + &:hover + background: $purple-dark + color: white !important + + .nutshell-list + li + border-top-color: rgba(90, 42, 130, 0.2) + color: $gray-300 !important + + svg + color: $yellow-accent !important + + .content + color: $gray-300 !important + + a + color: $yellow-accent !important + + &:hover + text-decoration: underline + color: $yellow-accent !important + + .font-semibold, + span.font-semibold + color: $gray-300 !important + + .quick-ref-list + li + border-bottom-color: rgba(90, 42, 130, 0.2) + + .label + color: $gray-400 !important + font-size: 14px !important + font-weight: 500 !important + + .value + color: $yellow-accent !important + font-size: 14px !important + font-weight: 600 !important + + a + color: $yellow-accent !important + font-size: 14px !important + font-weight: 600 !important + + &:hover + text-decoration: underline + color: $yellow-accent !important + + i + color: $yellow-accent !important + font-size: 14px !important + font-weight: 600 !important + + // Maximum specificity dark mode overrides + html.dark #page #project_container .project-show-redesign + .gradient-card .card-content h3 + color: white !important + + .quick-ref-list li + .label + color: $gray-400 !important + font-size: 14px !important + + .value + color: $yellow-accent !important + font-size: 14px !important + + a, + i + color: $yellow-accent + font-size: 14px !important + + .similar-projects-list .similar-project-item .similar-proj-name + color: $yellow-accent !important + + // Languages section dark mode override + .languages-content .language-legend .language-item + .language-name + color: $gray-300 !important + font-size: 12px !important + + a + color: $yellow-accent !important + font-size: 12px !important + + .language-percent + color: $gray-400 !important + font-size: 12px !important + + // Summary sections dark mode override + // (Consolidated into Summary Typography Final Guard block below) + + // Rating section specificity overrides consolidated into Ratings Final Guard below + +html.dark + .licenses-section + .section-title + color: white + + .license-card + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + &:hover + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + .license-header + border-bottom-color: rgba(90, 42, 130, 0.3) + + .header-left + svg + color: $yellow-accent + + h4 + color: $yellow-accent + + .chevron + color: #94a3b8 + + .license-content + border-top-color: rgba(90, 42, 130, 0.3) + + .license-details-grid + .license-detail-card + &.permitted + background: linear-gradient(to bottom right, #3a1d6e 0%, #4a2580 100%) + + h5 + color: $yellow-accent + + svg + color: $yellow-accent + + li + color: $gray-300 + + svg + color: $yellow-accent + + &.forbidden + background: linear-gradient(to bottom right, #4a1f1f 0%, #3a1515 100%) + + h5 + color: #fb923c + + svg + color: #fb923c + + li + color: $gray-300 + + svg + color: #fb923c + + &.required + background: linear-gradient(to bottom right, #4a3a15 0%, #3a2a10 100%) + + h5 + color: #facc15 + + svg + color: #facc15 + + li + color: $gray-300 + + svg + color: #facc15 + + .license-disclaimer + border-top-color: rgba(90, 42, 130, 0.3) + + p + color: #94a3b8 + + .view-all-link + a + font-weight: 500 !important + color: $yellow-accent !important + + &:visited, + &:active + color: $yellow-accent !important + + &:hover + text-decoration: underline + color: $yellow-accent !important + + .security-section + .section-title + color: white !important + + .card-subtitle + color: white !important + + .security-link + color: white !important + + &:hover + color: $yellow-accent !important + + .subtitle-detail + color: #9ca3af !important + + #vulnerability_version_chart + .chart-container + background: rgba(90, 42, 130, 0.15) + + .no-data + color: #94a3b8 + + .index-section + .index-title + color: #9ca3af !important + + .gradient-bar-container + .indicator-pointer + background: white !important + + &:hover + background: $yellow-accent !important + + .report-link-container + .report-link + color: $yellow-accent !important + + .did-you-know-card + background: linear-gradient(135deg, #4a3a15 0%, #3a2a10 100%) !important + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + .did-you-know-title + color: #fcd34d !important + + svg + color: #fcd34d !important + + .feature-list + li + color: #fde68a !important + + a + color: #fcd34d !important + + .security-footer-link + a + color: $yellow-accent !important + + &:hover + color: $yellow-accent !important + + .no-data + color: #94a3b8 + + // Legacy sections rendered inside redesign page (security + analysis summary) + .project_row + color: #e2e8f0 + + h2, h3, h4, h5 + color: #f8fafc !important + + p, small, td, th, li, span + color: #cbd5e1 + + a + color: $yellow-accent + + .well, .oh-card, + .activity_well, + .community_well, + #vulnerability-report .well, + #vulnerability-report .oh-card + background-color: #2D1548 !important + border-color: rgba(159, 122, 186, 0.3) !important + + .right_border + border-right-color: rgba(159, 122, 186, 0.3) !important + + .security_blog_link + color: $yellow-accent !important + + #did_you_know + background-color: transparent !important + + h4 + color: #f8fafc !important + + .well, .oh-card + background-color: #1D0631 !important + border-color: #4b5563 !important + + ul, + li, + .indent, + p, + span + color: #cbd5e1 !important + + a + color: $yellow-accent !important + + &:hover + color: $yellow-hover !important + text-decoration: underline + + .project_row + #did_you_know + .well, .oh-card + background-color: #1D0631 !important + border-color: #4b5563 !important + + .no_analysis_message + .alert.alert-info.alert-block + background-color: #2D1548 !important + border: 1px solid rgba(159, 122, 186, 0.35) !important + color: #cbd5e1 !important + + .title + h3 + color: #f8fafc !important + + i, + [class^='icon-'], + [class*=' icon-'] + color: $yellow-accent !important + + p, + .analysis_in_progress, + .indent, + span + color: #60a5fa !important + + a + color: $yellow-accent !important + + .btn.btn-primary, + .btn.btn-large.btn-primary + background-color: $yellow-accent !important + border-color: $yellow-accent !important + color: $purple-dark !important + + &:hover, + &:focus, + &:active + background-color: $yellow-hover !important + border-color: $yellow-hover !important + color: $purple-dark !important + +// ============================================================================ +// Final Override Layer - Ensure our styles take precedence +// ============================================================================ +#project_container .project-show-redesign + .gradient-card + .card-content + h3, + h4 + color: $gray-900 !important + + p:not(.summary-timespan):not(.summary-stat):not(.summary-change) + color: $gray-700 !important + + .nutshell-list li + color: $gray-700 !important + + .content + color: $gray-700 !important + + a + color: $purple-primary !important + + .quick-ref-list li + .label + color: $gray-600 !important + + .value + color: $purple-primary !important + + a + color: $purple-primary !important + +// Dark mode final overrides +html.dark #project_container .project-show-redesign + .gradient-card + .card-content + h3, + h4 + color: white !important + + p:not(.summary-timespan):not(.summary-stat):not(.summary-change) + color: #cbd5e1 !important + + .nutshell-list li + color: $gray-300 !important + + .content + color: $gray-300 !important + + a + color: $yellow-accent !important + + .quick-ref-list li + .label + color: $gray-400 !important + + .value + color: $yellow-accent !important + + a + color: $yellow-accent !important + +// ============================================================================ +// Analysis Grid (Code, Activity, Community) +// ============================================================================ +.project-show-redesign + .analysis-grid + display: grid + grid-template-columns: 1fr + gap: 16px + margin-bottom: 32px + + @media (min-width: 768px) + gap: 24px + margin-bottom: 48px + + @media (min-width: 1024px) + grid-template-columns: repeat(3, 1fr) + + .analysis-card + display: flex + flex-direction: column + + .card-content + display: flex + flex-direction: column + height: 100% + flex: 1 + + .section-header + font-size: 18px !important + font-weight: 600 !important + color: $gray-900 !important + margin-bottom: 12px !important + padding-bottom: 8px + border-bottom: 1px solid $gray-200 + + @media (min-width: 768px) + font-size: 20px !important + + .chart-title + font-size: 14px !important + font-weight: 600 !important + margin-bottom: 12px + display: block + + a + color: $purple-primary !important + text-decoration: none + + &:hover + text-decoration: underline + color: $purple-primary !important + + .subsection-title + font-size: 14px !important + font-weight: 600 !important + color: $gray-900 !important + margin-top: 16px + margin-bottom: 8px + + .chart-container + min-height: 160px + background: $gray-50 + border-radius: 8px + padding: 8px + margin-bottom: 16px + overflow: hidden + + @media (min-width: 768px) + min-height: 180px + + &.activity-chart + background: #f3f3f5 + padding: 12px + + #code_history_chart.chart-container + background: transparent + border-radius: 0 + padding: 0 + min-height: 200px + + #community_chart + margin-bottom: 16px + + .highcharts-series-0 + .highcharts-graph, + .highcharts-point + stroke: $purple-primary !important + fill: $purple-primary !important + + .highcharts-series-1 + .highcharts-point + stroke: $purple-primary !important + fill: $purple-primary !important + + .no-data + text-align: center + padding: 40px 20px + color: $gray-600 + font-size: 14px + + // Chart header with zoom controls + .chart-header + display: flex + align-items: flex-start + justify-content: space-between + margin-bottom: 12px + gap: 8px + + .chart-title + margin-bottom: 0 + + .zoom-controls + display: flex + align-items: center + gap: 4px + flex-shrink: 0 + + .zoom-label + font-size: 12px + color: $gray-400 + margin-right: 4px + + .zoom-btn + padding: 2px 6px + border-radius: 4px + font-size: 12px + border: none + background: transparent + color: $gray-500 + cursor: pointer + transition: all 0.15s + + &:hover + color: $purple-primary + + &.active + background: $purple-primary + color: white + font-weight: 600 + + // Summary Grid (30 day, 12 month) + .summary-grid + display: grid + grid-template-columns: 1fr + gap: 16px + margin-top: 16px + + @media (min-width: 768px) + grid-template-columns: repeat(2, 1fr) + gap: 20px + + .summary-section + .summary-header + font-size: 12px !important + font-weight: 600 !important + color: $gray-900 !important + margin-bottom: 2px !important + + .summary-timespan + font-size: 12px !important + color: $gray-400 !important + margin-bottom: 6px !important + line-height: 1.25 + + .summary-stat + font-size: 12px !important + color: $gray-700 !important + margin-bottom: 2px !important + line-height: 1.3 + + &:last-of-type + margin-bottom: 0 !important + + &.thirty-day-summary + .summary-header + margin-bottom: 1px !important + + .summary-timespan + margin-bottom: 6px !important + + .summary-stat + margin-bottom: 1px !important + line-height: 1.25 + + .stat-number + font-weight: 600 !important + color: $gray-900 !important + + .summary-change + font-size: 12px !important + margin-bottom: 8px !important + line-height: 1.4 + + &.positive + color: #16a34a !important + + &.negative + color: #dc2626 !important + +// ============================================================================ +// Summary Cascade Guard (override generic gradient-card paragraph rules) +// ============================================================================ +#project_container .project-show-redesign .analysis-grid .summary-section + .summary-header + margin-top: 0 !important + + .summary-timespan, + .summary-stat, + .summary-change + margin-top: 0 !important + + .summary-timespan + margin-bottom: 6px !important + + .summary-stat + margin-bottom: 2px !important + + .summary-change + margin-bottom: 8px !important + +// Summary dark mode colors consolidated into Summary Typography Final Guard below + +// ============================================================================ +// Languages + Contributors Cascade Guard (Redesign only) +// ============================================================================ +#project_container .project-show-redesign .analysis-grid .analysis-card .card-content + .languages-content + display: flex !important + align-items: flex-start !important + gap: 16px !important + margin-top: 8px !important + + .language-pie-container + width: 90px !important + height: 90px !important + flex: 0 0 90px !important + + .language-pie-chart + width: 90px !important + height: 90px !important + display: block !important + + .language-legend + flex: 1 1 auto !important + min-width: 0 !important + display: flex !important + flex-direction: column !important + gap: 6px !important + + .language-item + display: flex !important + align-items: center !important + gap: 8px !important + min-width: 0 !important + font-size: 12px !important + + .language-color-box + width: 10px !important + height: 10px !important + border-radius: 2px !important + flex-shrink: 0 !important + + .language-name + flex: 1 1 auto !important + min-width: 0 !important + white-space: nowrap !important + overflow: hidden !important + text-overflow: ellipsis !important + color: $gray-700 !important + font-size: 12px !important + + a + color: $purple-primary !important + text-decoration: none !important + font-size: 12px !important + + &:hover + text-decoration: underline !important + + .language-percent + flex: 0 0 auto !important + margin-left: auto !important + color: $gray-500 !important + font-size: 12px !important + font-weight: 500 !important + + .community-chart-wrap + .col-md-12.manage_padding + padding: 0 !important + .col-md-12 + h4 + a + text-decoration: none !important + + .contributors-grid + display: grid !important + grid-template-columns: repeat(2, minmax(0, 1fr)) !important + column-gap: 8px !important + row-gap: 6px !important + margin-top: 6px !important + + .contributor-item + display: flex !important + align-items: center !important + gap: 6px !important + min-width: 0 !important + + .contributor-avatar + width: 24px !important + height: 24px !important + border-radius: 50% !important + flex: 0 0 24px !important + + .contributor-name + min-width: 0 !important + white-space: nowrap !important + overflow: hidden !important + text-overflow: ellipsis !important + color: $purple-primary !important + text-decoration: none !important + + &:hover + text-decoration: underline !important + +html.dark #project_container .project-show-redesign .analysis-grid .analysis-card .card-content + .languages-content + .language-legend + .language-item + .language-name + color: $gray-300 !important + + a + color: $yellow-accent !important + + .language-percent + color: $gray-400 !important + + .contributors-grid + .contributor-item + .contributor-name + color: $yellow-accent !important + + // Languages Section (styles consolidated into Cascade Guard above) + + // Contributors Grid + .contributors-grid + display: grid + grid-template-columns: repeat(2, 1fr) + column-gap: 8px + row-gap: 6px + margin-top: 6px + + .contributor-item + display: flex + align-items: center + gap: 6px + + .contributor-avatar + width: 24px + height: 24px + border-radius: 50% + flex-shrink: 0 + + .contributor-name + font-size: 12px !important + line-height: 1.2 + color: $purple-primary + text-decoration: none + white-space: nowrap + overflow: hidden + text-overflow: ellipsis + + &:hover + text-decoration: underline + + // Ratings Section + .ratings-section + margin-top: 12px + padding-top: 12px + border-top: 1px solid $gray-200 + + .rating-content + .rating-info + font-size: 12px !important + line-height: 1.3 + color: $gray-700 + margin-bottom: 2px + + .rating-link + color: $purple-primary + text-decoration: none + + &:hover + text-decoration: underline + + .rating-display + display: flex + align-items: center + gap: 6px + + .stars-container + display: inline-block + height: 23px + line-height: 23px + overflow: hidden + + .rating_stars, + [id$='_rating_stars'] + display: inline-block + height: 23px + line-height: 1 + + // Allow the rating stars helper's inline styles to work + span[style*='float: left'] + float: left !important + display: inline !important + height: 23px + + .rating-value + font-size: 12px !important + color: $gray-900 !important + font-weight: 600 !important + + // SVG display stars (average) + .stars-display + display: flex + align-items: center + gap: 2px + + .star-icon + display: inline-block + vertical-align: middle + flex-shrink: 0 + + &.star-filled, + &.star-half + color: #ffb91a + + &.star-empty + color: $gray-300 + + // Interactive star buttons (user rating) + .interactive-stars + display: flex + gap: 2px + margin-top: 2px + + .star-btn + background: none + border: none + padding: 0 + cursor: pointer + line-height: 1 + transition: transform 0.1s ease + + &:hover + transform: scale(1.15) + + .star-svg + display: block + color: $gray-300 + transition: fill 0.1s ease, stroke 0.1s ease + + .rating-user-score + font-size: 10px !important + color: $gray-500 + margin-top: 2px + +// ============================================================================ +// Dark Mode - Analysis Grid +// ============================================================================ +html.dark .project-show-redesign + .section-header + color: white !important + border-bottom-color: rgba(90, 42, 130, 0.3) + + .chart-title + a + color: $yellow-accent !important + + &:hover + color: $yellow-accent !important + + .subsection-title + color: white !important + + .no-data + color: #94a3b8 !important + + .chart-container + background: rgba(90, 42, 130, 0.15) + + &.activity-chart + background: #1D0631 + + #code_history_chart.chart-container + background: transparent + + .zoom-controls + .zoom-label + color: #94a3b8 + + .zoom-btn + color: #94a3b8 + + &:hover + color: $yellow-accent + + &.active + background: $yellow-accent + color: #1D0631 + + // Summary section dark colors: see Summary Typography Final Guard below + // Languages, contributors, ratings dark colors: see respective Final Guard blocks below + +// ============================================================================ +// Ratings Final Guard (Figma parity) +// ============================================================================ +#project_container .project-show-redesign .analysis-grid .analysis-card .card-content + .ratings-section + margin-top: 12px !important + padding-top: 12px !important + border-top: 1px solid $gray-200 !important + + .subsection-title + margin-top: 0 !important + margin-bottom: 8px !important + font-size: 14px !important + font-weight: 600 !important + color: $gray-900 !important + + .rating-content + display: flex !important + flex-direction: column !important + gap: 2px !important + + .community_rating_header + font-size: 14px !important + font-weight: 600 !important + margin: 0 0 4px 0 !important + + a + text-decoration: none !important + + &:hover + text-decoration: none !important + + .rating-top-row + display: flex !important + align-items: flex-start !important + justify-content: space-between !important + gap: 12px !important + + .rating-main + flex: 1 1 auto !important + min-width: 0 !important + + .rating-action + flex: 0 0 auto !important + text-align: right !important + + .rating-link + display: inline-flex !important + align-items: center !important + justify-content: flex-end !important + gap: 6px !important + + .rating-action-icon + width: 12px !important + height: 12px !important + flex-shrink: 0 !important + + .rating-secondary + margin-top: 4px !important + white-space: normal !important + + .rating-info + margin: 0 !important + font-size: 12px !important + line-height: 1.35 !important + color: $gray-700 !important + + &.rating-secondary + margin-top: 4px !important + + .rating-link + color: $purple-primary !important + text-decoration: none !important + font-weight: 500 !important + + &:hover + color: $purple-primary !important + text-decoration: underline !important + + .rating-display + display: flex !important + align-items: center !important + gap: 6px !important + margin-top: 2px !important + + .stars-container + display: inline-block !important + height: 23px !important + line-height: 23px !important + overflow: hidden !important + min-width: 112px !important + flex-shrink: 0 !important + + &::after + content: "" + display: table + clear: both + + .rating_stars, + [id$='_rating_stars'] + display: inline-block !important + height: 23px !important + + span + vertical-align: top !important + + .rating-value + font-size: 12px !important + line-height: 1.2 !important + font-weight: 600 !important + color: $gray-900 !important + +html.dark #project_container .project-show-redesign .analysis-grid .analysis-card .card-content + .ratings-section + border-top-color: rgba(90, 42, 130, 0.3) !important + + .subsection-title + color: white !important + + .rating-content + .community_rating_header + a + color: white !important + + .rating-top-row + .rating-action + .rating-info + color: $gray-300 !important + + .rating-info + color: $gray-300 !important + + .rating-link + color: $yellow-accent !important + + &:hover + color: $yellow-accent !important + + .rating-display + .rating-value + color: white !important + + .stars-display + .star-icon + &.star-filled, + &.star-half + color: #ffb91a !important + + &.star-empty + color: #4b5563 !important + + .interactive-stars + .star-btn + .star-svg + color: #4b5563 !important + + .rating-user-score + color: $gray-400 !important + +// ============================================================================ +// Dark Theme Final Guard (Licenses + Graphs) +// ============================================================================ +html.dark #project_container .project-show-redesign + .licenses-section + .section-title + color: white !important + + .license-card + background: #2D1548 !important + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) !important + + .license-header + border-bottom-color: rgba(90, 42, 130, 0.3) !important + + .header-left + h4 + color: $yellow-accent !important + + svg + color: $yellow-accent !important + + .chevron + color: #94a3b8 !important + + .license-content + border-top-color: rgba(90, 42, 130, 0.3) !important + + .license-disclaimer + border-top-color: rgba(90, 42, 130, 0.3) !important + + p + color: #94a3b8 !important + + .view-all-link a + color: $yellow-accent !important + + // Graph surfaces + chart typography + #activity_chart, + #community_chart, + #loc_chart, + #vulnerability_version_chart + .highcharts-background + fill: transparent !important + + .highcharts-plot-background, + .highcharts-plot-border, + .highcharts-label-box + fill: none !important + + .highcharts-axis-line, + .highcharts-tick, + .highcharts-grid-line + stroke: rgba(148, 163, 184, 0.35) !important + + .highcharts-axis-labels text, + .highcharts-axis-title, + .highcharts-legend-item text, + .highcharts-title, + .highcharts-subtitle + fill: #cbd5e1 !important + + .highcharts-tooltip-box + fill: #2D1548 !important + stroke: #5A2A82 !important + + .highcharts-tooltip text + fill: #f8fafc !important + + +// ============================================================================ +// Summary Typography Final Guard (Figma parity) +// ============================================================================ +#project_container .project-show-redesign .analysis-grid .analysis-card .card-content + .summary-grid + .summary-section + h5.summary-header + font-size: 12px !important + font-weight: 600 !important + line-height: 1.25 !important + margin: 0 0 2px 0 !important + color: $gray-900 !important + + p.summary-timespan, + p.summary-stat, + p.summary-change + font-size: 12px !important + line-height: 1.35 !important + margin-top: 0 !important + + p.summary-timespan + color: $gray-400 !important + margin-bottom: 6px !important + + p.summary-stat + color: $gray-700 !important + margin-bottom: 2px !important + + p.summary-change + margin-bottom: 8px !important + + .stat-number + font-size: 12px !important + font-weight: 600 !important + line-height: 1.25 !important + color: $gray-900 !important + +html.dark #project_container .project-show-redesign .analysis-grid .analysis-card .card-content + .summary-grid + .summary-section + h5.summary-header + color: white !important + + p.summary-timespan + color: #94a3b8 !important + + p.summary-stat + color: #d1d5db !important + + .stat-number + color: white !important + +// ============================================================================ +// Dark Mode Compatibility Guard (html.dark + body.dark) +// ============================================================================ +html.dark #project_container .project-show-redesign, +body.dark #project_container .project-show-redesign, +.dark #project_container .project-show-redesign + .licenses-section + .section-title + color: white !important + + .license-card + background: #2D1548 !important + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) !important + + .license-header + border-bottom-color: rgba(90, 42, 130, 0.3) !important + + .header-left + h4 + color: $yellow-accent !important + + svg + color: $yellow-accent !important + + .chevron + color: #94a3b8 !important + + .license-content + border-top-color: rgba(90, 42, 130, 0.3) !important + + .license-detail-card + &.permitted, + &.forbidden, + &.required + border: 1px solid rgba(159, 122, 186, 0.25) !important + + &.permitted + background: linear-gradient(to bottom right, #3a1d6e 0%, #4a2580 100%) !important + + h5, + h5 svg, + li svg + color: $yellow-accent !important + + li + color: $gray-300 !important + + &.forbidden + background: linear-gradient(to bottom right, #4a1f1f 0%, #3a1515 100%) !important + + h5, + h5 svg, + li svg + color: #fb923c !important + + li + color: $gray-300 !important + + &.required + background: linear-gradient(to bottom right, #4a3a15 0%, #3a2a10 100%) !important + + h5, + h5 svg, + li svg + color: #facc15 !important + + li + color: $gray-300 !important + + .license-disclaimer + border-top-color: rgba(90, 42, 130, 0.3) !important + + p + color: #94a3b8 !important + + .view-all-link a + color: $yellow-accent !important + +// ============================================================================ +// Dark Mode - Login Modal & Add to Stacks Modal +// ============================================================================ +html.dark + #LoginModal + .modal-content + background-color: #1D0631 + border: 1px solid rgba(90, 42, 130, 0.4) + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5) + + .modal-header + border-bottom-color: rgba(90, 42, 130, 0.3) + + .modal-title + color: white + + .close + color: #94a3b8 + opacity: 0.8 + text-shadow: none + + &:hover + color: white + opacity: 1 + + .modal-body, + .model-body + color: #d1d5db + + .well, .oh-card + background-color: #2D1548 + border-color: rgba(90, 42, 130, 0.3) + box-shadow: none + + legend + color: white + border-bottom-color: rgba(90, 42, 130, 0.3) + + .control-group + label, + .checkbox + color: #d1d5db + + input[type="text"], + input[type="password"], + input[type="email"] + background-color: #2D1548 + border-color: rgba(159, 122, 186, 0.3) + color: white + + &::placeholder + color: #6b7280 + + &:focus + border-color: #FFB91A + box-shadow: 0 0 0 2px rgba(255, 185, 26, 0.2) + + .add-on + background-color: #2D1548 + border-color: rgba(159, 122, 186, 0.3) + color: #9ca3af + + i + color: #9ca3af + + // Stack checkbox rows (legacy — now handled by .stack-entries-* scoped styles) + + .modal-footer + border-top-color: rgba(90, 42, 130, 0.3) + background-color: #1D0631 + + .btn-primary + background-color: #FFB91A !important + border-color: #FFB91A !important + color: #1D0631 !important + + &:hover, + &:focus + background-color: #FFCC4D !important + border-color: #FFCC4D !important + +// Dark mode for vulnerability table — scoped to #vulnerabilities-data to avoid affecting other tables +html.dark #vulnerabilities-data .table-responsive > .table-striped + background-color: #1D0631 !important + +html.dark #vulnerabilities-data .table-responsive > .table-striped > thead > tr > th + background-color: #1D0631 !important + color: #d1d5db !important + border-bottom-color: rgba(90,42,130,0.4) !important + +html.dark #vulnerabilities-data .table-responsive > .table-striped > tbody > tr > td, +html.dark #vulnerabilities-data .table-responsive > .table-striped > tbody > tr > th + background-color: #2D1548 !important + color: #d1d5db !important + border-top-color: rgba(90,42,130,0.2) !important + +html.dark #vulnerabilities-data .table-responsive > .table-striped > tbody > tr:nth-child(odd) > td, +html.dark #vulnerabilities-data .table-responsive > .table-striped > tbody > tr:nth-child(odd) > th + background-color: rgba(29,6,49,0.8) !important + +html.dark #vulnerabilities-data .table-responsive > .table-striped > tbody > tr > td a + color: #ffb91a !important + + &:hover + color: #ffcc4d !important + +// ============================================================================ +// Add to Stacks Modal — Responsive Upgrade +// ============================================================================ +#stack-entries-modal + .stack-entries-dialog + width: 100% + max-width: 520px + margin: 1.75rem auto + + @media (max-width: 576px) + max-width: 100% + margin: 0.5rem + + .stack-entries-content + border-radius: 12px + overflow: hidden + border: none + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15) + + .stack-entries-header + display: flex + align-items: center + justify-content: space-between + padding: 1.25rem 1.5rem + border-bottom: 1px solid #e9ecef + background: linear-gradient(135deg, #f8f5ff 0%, #ffffff 100%) + + .stack-entries-title + font-size: 1.1rem + font-weight: 600 + color: #2d1548 + margin: 0 + display: flex + align-items: center + gap: 0.5rem + + .fa + color: #7c3aed + + .stack-entries-close + margin: 0 + padding: 0.25rem 0.5rem + font-size: 1.4rem + line-height: 1 + color: #6b7280 + opacity: 1 + float: none + order: 1 + + &:hover + color: #1f2937 + opacity: 1 + + .stack-entries-body + padding: 1.25rem 1.5rem + max-height: 55vh + overflow-y: auto + + @media (max-width: 576px) + max-height: 60vh + padding: 1rem + + .stack-entries-subtitle + font-size: 0.95rem + color: #4b5563 + margin-bottom: 1rem + text-align: center + + .stack-entries-list + display: flex + flex-direction: column + gap: 0.5rem + + .stack-entries-row + display: flex + align-items: center + justify-content: space-between + padding: 0.6rem 0.75rem + border-radius: 8px + border: 1px solid #e9ecef + background-color: #fafafa + transition: background-color 0.15s ease, border-color 0.15s ease + + &:hover + background-color: #f3f0ff + border-color: #c4b5fd + + @media (max-width: 576px) + flex-wrap: wrap + gap: 0.4rem + + .stack-entries-row-left + display: flex + align-items: center + flex: 1 + min-width: 0 + + .stack-entries-row-right + display: flex + align-items: center + gap: 0.5rem + flex-shrink: 0 + + .stack-entries-checkbox-wrap + display: flex + align-items: center + gap: 0.6rem + width: 100% + + .stack-entries-checkbox + width: 16px + height: 16px + flex-shrink: 0 + cursor: pointer + accent-color: #7c3aed + + .stack-entries-label + display: flex + align-items: center + gap: 0.4rem + font-size: 0.9rem + color: #374151 + cursor: pointer + margin: 0 + white-space: nowrap + overflow: hidden + text-overflow: ellipsis + + .stack-entries-stack-icon + color: #7c3aed + font-size: 0.8rem + + .stack-entries-spinner + width: 16px + height: 16px + + .stack-entries-message + font-size: 0.8rem + color: #059669 + + .stack-entries-footer + padding: 1rem 1.5rem + border-top: 1px solid #e9ecef + background-color: #fafafa + + @media (max-width: 576px) + padding: 0.75rem 1rem + + .stack-entries-new-form + margin: 0 + + .stack-entries-submit + width: 100% + font-size: 0.95rem + font-weight: 600 + padding: 0.6rem 1rem + border-radius: 8px + +// Dark mode — Add to Stacks Modal +html.dark #stack-entries-modal + .stack-entries-content + background-color: #1D0631 + border: 1px solid rgba(90, 42, 130, 0.4) + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5) + + .stack-entries-header + background: linear-gradient(135deg, #2D1548 0%, #1D0631 100%) + border-bottom-color: rgba(90, 42, 130, 0.3) + + .stack-entries-title + color: #f3f4f6 + + .fa + color: #a78bfa + + .stack-entries-close + color: #94a3b8 + + &:hover + color: #f3f4f6 + + .stack-entries-subtitle + color: #9ca3af + + .stack-entries-row + background-color: #2D1548 + border-color: rgba(90, 42, 130, 0.3) + + &:hover + background-color: #3d1f60 + border-color: rgba(159, 122, 186, 0.5) + + .stack-entries-label + color: #d1d5db + + .stack-entries-stack-icon + color: #a78bfa + + .stack-entries-message + color: #6ee7b7 + + .stack-entries-footer + background-color: #1D0631 + border-top-color: rgba(90, 42, 130, 0.3) + + .stack-entries-submit + background-color: #FFB91A !important + border-color: #FFB91A !important + color: #1D0631 !important + + &:hover, + &:focus + background-color: #FFCC4D !important + border-color: #FFCC4D !important + +// ============================================================================ +// Stacks Index Page — Responsive Upgrade +// ============================================================================ +.stacks-index-page + max-width: 1200px + margin: 0 auto + padding: 24px 16px + + @media (max-width: 576px) + padding: 16px 12px + +.stacks-page-header + display: flex + align-items: center + justify-content: space-between + flex-wrap: wrap + gap: 12px + margin-bottom: 24px + padding-bottom: 12px + border-bottom: 2px solid #e9ecef + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + +.stacks-page-title + font-size: 26px + font-weight: 700 + color: #1f2937 + margin: 0 + + html.dark & + color: #f3f4f6 + + @media (max-width: 576px) + font-size: 21px + +.stacks-new-btn + display: inline-flex + align-items: center + gap: 6px + border-radius: 8px + font-weight: 600 + padding: 8px 16px + white-space: nowrap + + @media (max-width: 400px) + font-size: 14px + padding: 6px 12px + +.stacks-layout + display: grid + grid-template-columns: 1fr 280px + gap: 24px + align-items: start + + @media (max-width: 900px) + grid-template-columns: 1fr + + @media (max-width: 576px) + gap: 16px + +.stacks-main + min-width: 0 + +.stacks-card + margin-bottom: 16px + border-radius: 12px + transition: box-shadow 0.2s ease, transform 0.15s ease + + &:hover + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12) + transform: translateY(-1px) + + html.dark &:hover + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4) + +.stacks-card-header + display: flex + align-items: flex-start + justify-content: space-between + gap: 12px + padding: 16px 20px 8px + + @media (max-width: 480px) + padding: 12px 16px 6px + +.stacks-card-title + font-size: 17px + font-weight: 600 + margin: 0 + min-width: 0 + +.stacks-card-name + color: #7c3aed + text-decoration: none + word-break: break-word + + &:hover + color: #5b21b6 + text-decoration: underline + + html.dark & + color: #a78bfa + + html.dark &:hover + color: #c4b5fd + +.stacks-delete-btn + flex-shrink: 0 + border-radius: 6px + font-size: 14px + padding: 5px 12px + line-height: 1.4 + +.stacks-card-desc-link + display: block + padding: 0 20px + text-decoration: none + + @media (max-width: 480px) + padding: 0 16px + +.stacks-card-desc + font-size: 16px + color: #4b5563 + margin: 4px 0 8px + line-height: 1.6 + + html.dark & + color: #d1d5db + +.stacks-card-projects + display: flex + flex-wrap: wrap + gap: 6px + padding: 8px 20px + + @media (max-width: 480px) + padding: 8px 16px + +.stacks-card-footer + padding: 8px 20px 12px + border-top: 1px solid #f3f4f6 + + html.dark & + border-top-color: rgba(90, 42, 130, 0.2) + + @media (max-width: 480px) + padding: 8px 16px 10px + +.stacks-embed-link + font-size: 14px + color: #6b7280 + text-decoration: none + display: inline-flex + align-items: center + gap: 6px + + &:hover + color: #7c3aed + + html.dark & + color: #9ca3af + + html.dark &:hover + color: #a78bfa + +// Empty state +.stacks-empty-state + text-align: center + padding: 48px 16px + background-color: #fafafa + border-radius: 12px + border: 2px dashed #e5e7eb + + html.dark & + background-color: #2D1548 + border-color: rgba(90, 42, 130, 0.3) + +.stacks-empty-icon + font-size: 48px + color: #d1d5db + margin-bottom: 16px + display: block + + html.dark & + color: rgba(167, 139, 250, 0.3) + +.stacks-empty-text + font-size: 16px + color: #6b7280 + margin: 0 + + html.dark & + color: #9ca3af + +.stacks-empty-link + color: #7c3aed + font-weight: 600 + text-decoration: none + + &:hover + text-decoration: underline + + html.dark & + color: #a78bfa + +// Sidebar info card +.stacks-sidebar + position: sticky + top: 16px + + @media (max-width: 900px) + position: static + order: -1 + +.stacks-info-card + border-radius: 12px + padding: 0 + +.stacks-info-title + font-size: 15px + font-weight: 600 + color: #374151 + margin: 0 + padding: 16px 20px 12px + border-bottom: 1px solid #e9ecef + display: flex + align-items: center + gap: 6px + + .fa + color: #7c3aed + + html.dark & + color: #d1d5db + border-bottom-color: rgba(90, 42, 130, 0.2) + + html.dark & .fa + color: #a78bfa + +.stacks-info-text + font-size: 14px + color: #6b7280 + line-height: 1.6 + margin: 0 + padding: 10px 20px + + &:last-child + padding-bottom: 16px + + html.dark & + color: #9ca3af + + a + color: #7c3aed + text-decoration: none + + &:hover + text-decoration: underline + + html.dark & + color: #a78bfa + +// ============================================================================ +// Similar Projects Page — Responsive Upgrade +// ============================================================================ +.similar-page + max-width: 1200px + margin: 0 auto + padding: 24px 16px + + @media (max-width: 576px) + padding: 16px 12px + +.similar-page-header + margin-bottom: 24px + padding-bottom: 12px + border-bottom: 2px solid #e9ecef + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + +.similar-page-title + font-size: 26px + font-weight: 700 + color: #1f2937 + margin: 0 + + html.dark & + color: #f3f4f6 + + @media (max-width: 576px) + font-size: 21px + +.similar-section + margin-bottom: 40px + +.similar-section-heading + font-size: 18px + font-weight: 600 + color: #374151 + margin: 0 0 16px + display: flex + flex-wrap: wrap + align-items: baseline + gap: 6px + + html.dark & + color: #e5e7eb + + @media (max-width: 576px) + font-size: 16px + +.similar-project-name + color: #7c3aed + font-weight: 700 + + html.dark & + color: #a78bfa + +// Responsive card grid +.similar-grid + display: grid + grid-template-columns: repeat(3, 1fr) + gap: 16px + + @media (max-width: 900px) + grid-template-columns: repeat(2, 1fr) + + @media (max-width: 560px) + grid-template-columns: 1fr + +// Individual project card +.similar-card + display: flex + flex-direction: column + border-radius: 12px + overflow: hidden + transition: box-shadow 0.2s ease, transform 0.15s ease + + &:hover + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12) + transform: translateY(-2px) + + html.dark &:hover + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4) + +.similar-card-header + display: flex + align-items: flex-start + gap: 12px + padding: 16px 16px 10px + +.similar-card-icon + flex-shrink: 0 + + img + border-radius: 6px + display: block + +.similar-card-meta + flex: 1 + min-width: 0 + +.similar-card-title + font-size: 15px + font-weight: 600 + margin: 0 0 4px + display: flex + flex-wrap: wrap + align-items: center + gap: 8px + +.similar-card-name + color: #7c3aed + text-decoration: none + word-break: break-word + + &:hover + color: #5b21b6 + text-decoration: underline + + html.dark & + color: #a78bfa + + html.dark &:hover + color: #c4b5fd + +.similar-compare-btn + font-size: 12px + color: #6b7280 + text-decoration: none + white-space: nowrap + padding: 2px 8px + border: 1px solid #e5e7eb + border-radius: 4px + line-height: 1.4 + + &:hover + color: #7c3aed + border-color: #a78bfa + background-color: #f5f3ff + + html.dark & + color: #9ca3af + border-color: rgba(90, 42, 130, 0.4) + + html.dark &:hover + color: #a78bfa + background-color: rgba(90, 42, 130, 0.2) + border-color: rgba(167, 139, 250, 0.5) + +.similar-card-activity + font-size: 13px + color: #6b7280 + position: relative + + html.dark & + color: #9ca3af + +.similar-card-body + display: flex + flex-direction: column + gap: 6px + padding: 8px 16px 14px + border-top: 1px solid #f3f4f6 + + html.dark & + border-top-color: rgba(90, 42, 130, 0.2) + +.similar-card-row + display: flex + align-items: baseline + flex-wrap: wrap + gap: 4px + font-size: 13px + +.similar-card-label + color: #9ca3af + font-size: 12px + white-space: nowrap + + html.dark & + color: #6b7280 + +.similar-card-value + color: #374151 + font-size: 13px + + html.dark & + color: #d1d5db + +.similar-card-value-link + color: #7c3aed + text-decoration: none + font-size: 13px + + &:hover + text-decoration: underline + + html.dark & + color: #a78bfa + +// Empty state +.similar-empty-state + padding: 32px 16px + background-color: #fafafa + border-radius: 12px + border: 2px dashed #e5e7eb + text-align: center + + html.dark & + background-color: #2D1548 + border-color: rgba(90, 42, 130, 0.3) + +.similar-empty-text + font-size: 15px + color: #6b7280 + margin: 0 0 8px + + html.dark & + color: #9ca3af + +.similar-action-link + color: #7c3aed + font-weight: 600 + text-decoration: none + + &:hover + text-decoration: underline + + html.dark & + color: #a78bfa + +.similar-muted + color: #9ca3af + font-size: 13px + + html.dark & + color: #6b7280 diff --git a/app/assets/stylesheets/projects.sass b/app/assets/stylesheets/projects.sass index 2187fe1e3..24533adb2 100644 --- a/app/assets/stylesheets/projects.sass +++ b/app/assets/stylesheets/projects.sass @@ -1,3 +1,30 @@ +// Design System Variables for Redesigned Header +// ============================================================================ +$purple-dark: #1D0631 +$purple-primary: #5A2A82 +$purple-light: #9F7ABA +$yellow-accent: #FFB91A +$yellow-hover: #FFCC4D + +// Neutrals +$gray-50: #f9fafb +$gray-100: #f3f4f6 +$gray-200: #e5e7eb +$gray-300: #d1d5db +$gray-400: #9ca3af +$gray-500: #6b7280 +$gray-600: #4b5563 +$gray-700: #374151 +$gray-900: #111827 + +// Shadows +$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05) +$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) +$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) + +// System Font Stack +$font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif + $sizes: (('fifteen', 15, 1), ('twenty', 20, 0), ('twentyfive', 25, 19), ('thirtyfive', 35, 23)) $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_low', 4), ('inactive', 5), ('na', 6), ('new', 7)) @@ -28,13 +55,10 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ background-position: -#{($multiplier * $dimension)+($multiplier*0.8)}px 0 background-repeat: no-repeat position: absolute - top: 1rem + top: 16px .margin_top_two - margin-top: 2rem - -.thirtyfive_project_activity_text - width: 200px + margin-top: 32px .row-eq-height display: -webkit-box @@ -42,77 +66,6 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ display: -ms-flexbox display: flex -#projects_index_list, #managed_projects_list - margin: 20px 0 0 0 - .well - padding: 15px - h2 - line-height: inherit - .compare - margin: -15px -15px 0 0 - .sp_form.styled - background-color: #DBE4EE - padding: 6px 4px 6px 10px - margin: 0 - border-radius: 0 4px 0 0 - -webkit-border-radius: 0 4px 0 0 - &.selected - background-color: #0088cc - color: #fff - .logo - margin: 25px 20px 25px 0 - .info - width: 490px - i - float: right - .claimed-by-org - margin: 0 15px 0 10px - .desc - margin-bottom: 10px - &.last - display: none - margin-bottom: 20px - .stats - margin: 0 15px 0 0 - span - margin-left: 10px - a - width: 80px - text-align: right - display: inline-block - font-weight: bold - font-size: 1.2em - span - margin-left: 0 - font-size: 12px - .reviews-and-pai - position: relative - @each $level in $pai_levels - .twentyfive_project_activity_level_#{nth($level, 1)} - margin-left: 0 - top: 0 - .twentyfive_project_activity_text - margin: -5px 0 0 25px - text-align: center - width: 65px - min-height: 35px - .stars - margin: 10px 0 5px 4px - .reviews - clear: left - width: 85px - text-align: center - .add-info - clear: left - width: 574px - .main_language - margin-left: 85px - a - text-decoration: underline - .tags - clear: left - margin-top: 10px - .cocomo-form border: 2px solid #E0E4E8 .control-label @@ -120,6 +73,20 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ .caption background-color: #E0E4E8 padding: 0.5em + @media (max-width: 767px) + .input-prepend.input-append + display: flex + width: 100% + box-sizing: border-box + .add-on + flex: 0 0 44px + width: 44px + box-sizing: border-box + input#cocomo_salary + width: 0 + min-width: 0 + text-align: right + box-sizing: border-box #cocomo_page li @@ -128,112 +95,417 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ li margin: 0 width: 130px - overflow: auto + overflow: edit_authorized + .oh-card + h4 + margin-bottom: 0.8em + +.project-header-gradient + width: 100vw + margin-left: calc(-50vw + 50%) + margin-right: calc(-50vw + 50%) + background: linear-gradient(135deg, $purple-dark 0%, $purple-primary 100%) + padding: 32px 16px 32px + position: relative + overflow: hiddenßß -#project_masthead - height: 85px + .header-content + padding: 0px 10px + margin: 0 auto + position: relative + z-index: 10 - #project_icon - margin-top: 8px + .header-main + display: flex + flex-direction: column + gap: 16px - #widgets - width: 31rem + @media (min-width: 768px) + flex-direction: row + align-items: flex-start + justify-content: space-between + gap: 24px - #project_header - padding-right: 0px - .project_title - margin: 0px 0px 0px 0px - h1 - margin: 15px 0px 6px 0px - i - @include project-icon-colors + a, + button + font-family: $font-family + + // Top-right user stats and button (mobile and desktop) + .header-top-right + display: flex + align-items: center + gap: 12px + margin-top: 24px + z-index: 20 + + @media (min-width: 640px) + position: absolute + top: 49px + right: 24px + margin-top: 0 + + @media (min-width: 1024px) + right: 32px + + .activity-indicator-wrapper + display: flex + flex-direction: column + align-items: center + justify-content: center + gap: 8px + min-width: 80px + + [class^='thirtyfive_project_activity_level_'] + position: static + top: auto + margin-left: 0 + transform: scale(1.25) + transform-origin: center center + + .thirtyfive_project_activity_text + margin: 0 + width: auto + line-height: 1.2 + font-size: 12px + font-weight: 600 + color: white + text-align: center + + .stats-card + background: white + border-radius: 8px + padding: 12px 16px + box-shadow: $shadow-md + min-width: 140px + + .stats-card + .stats-inner + display: flex + align-items: center + gap: 12px + margin-bottom: 12px + + svg + width: 20px + height: 20px + color: $purple-primary + flex-shrink: 0 + + .user-count + font-size: 28px + font-weight: 700 + color: $purple-primary + line-height: 1 + + .i-use-this-btn + width: 100% + background-color: #ffb91a !important + color: #1D0631 !important + + &:hover + background-color: #ffcc4d !important + + &.using + background-color: #5A2A82 !important + color: #ffffff !important + + &:hover + background-color: #4a1f6e !important + + html.dark & + background-color: #ffb91a !important + color: #1D0631 !important + + &:hover + background-color: #ffcc4d !important + + html.dark &.using + background-color: #5A2A82 !important + color: #ffffff !important + + &:hover + background-color: #4a1f6e !important + + // Project title and info section + .project-title-section + flex: 1 + min-width: 0 + max-width: 900px + + @media (min-width: 768px) + max-width: calc(100% - 280px) + + h1 + font-size: 32px + font-weight: 700 + color: white + line-height: 1.2 + letter-spacing: -0.02em + margin: 0 0 12px + + a + color: white !important + text-decoration: none + &:hover + text-decoration: none + + @media (min-width: 768px) + font-size: 48px + + @media (min-width: 1024px) + font-size: 48px + + .action-buttons + display: flex + flex-wrap: wrap + gap: 8px + margin-bottom: 20px + + .action-btn + display: inline-flex + align-items: center + gap: 6px + padding: 6px 12px + border-radius: 8px + font-size: 12px + font-weight: 500 + line-height: 1.25 + text-decoration: none !important + appearance: none + -webkit-appearance: none + background-image: none !important + box-shadow: none !important + border: 0 + cursor: pointer + transition: all 0.2s ease + + &:link, + &:visited, + &:hover, + &:active + color: white !important + text-decoration: none !important + + @media (min-width: 768px) + font-size: 14px + + svg + width: 14px + height: 14px + flex-shrink: 0 + stroke: currentColor + + // Outlined button style (for Settings and Report Duplicate) + &.action-btn-outline + background: rgba(255, 255, 255, 0.1) + border: 1px solid rgba(255, 255, 255, 0.2) + color: white !important + box-shadow: none + + &:hover + background: rgba(255, 255, 255, 0.2) + border-color: rgba(255, 255, 255, 0.2) + color: white !important + transform: none + + &:active + transform: none + background: rgba(255, 255, 255, 0.2) + + &:focus-visible + outline: 2px solid rgba(255, 255, 255, 0.55) + outline-offset: 2px + + .project-description + font-size: 16px + color: rgba(255, 255, 255, 0.9) + line-height: 1.6 + margin-bottom: 12px + + @media (min-width: 768px) + font-size: 17px + + // Mobile Stats Section + .header-mobile-stats + display: flex + gap: 12px + margin-top: 4px + + @media (min-width: 640px) + display: none - .about - margin-top: 10px - li - margin: 15px 0 15px 30px -#add_this - margin-left: 5.3rem - margin-top: 5.3rem - a - @include add-this-link-colors - -#project_container - #project_masthead - #widgets - width: 30rem // settings page styles + +// Settings page — matches Figma ProjectSettingsView design +// Uses !important to override global h2 rules in base.sass +.settings-page-header + margin-top: 24px + margin-bottom: 24px + + @media (min-width: 640px) + margin-bottom: 32px + + .settings-page-header-row + display: flex + align-items: center + gap: 12px + margin-bottom: 4px + + .settings-page-icon + width: 36px + height: 36px + min-width: 36px + border-radius: 12px + background: linear-gradient(135deg, #5A2A82, #1D0631) + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) + i + color: #ffffff + font-size: 18px + margin: 0 + line-height: 1 + + h2.settings-page-h2 + font-size: 24px !important + font-weight: 700 !important + color: #111827 + margin: 0 !important + line-height: 1.2 !important + letter-spacing: -0.02em + padding: 0 + + @media (min-width: 320px) + font-size: 24px !important + + .settings-page-description + font-size: 14px + color: #6b7280 + line-height: 1.5 + margin: 0 0 0 48px + font-style: normal + +.settings-grid + display: grid + grid-template-columns: 1fr + gap: 16px + margin-bottom: 32px + + @media (min-width: 640px) + grid-template-columns: repeat(2, 1fr) + gap: 20px + + @media (min-width: 1024px) + grid-template-columns: repeat(3, 1fr) + margin-bottom: 40px + .settings_module - :cursor pointer + display: flex + align-items: flex-start + gap: 16px + padding: 20px + padding-right: 44px + background: #ffffff + border-radius: 16px + border: 1px solid #e5e7eb + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) + text-decoration: none + cursor: pointer + transition: all 0.2s ease + position: relative + + &::after + content: '\f054' + font-family: FontAwesome, sans-serif + font-weight: 900 + font-size: 14px + color: #9ca3af + position: absolute + top: 22px + right: 20px + transition: all 0.2s ease + line-height: 1 + &:hover + border-color: rgba(90, 42, 130, 0.6) + box-shadow: 0 4px 12px rgba(90, 42, 130, 0.1) + text-decoration: none + .settings-icon-wrapper + transform: scale(1.05) h4 - text-decoration: underline - .module_icon - &.basics - background-position: 0 -40px - &.code_locations - background-position: -40px -40px - &.aliases - background-position: -80px -40px - &.logo - background-position: -120px -40px - &.links - background-position: -160px -40px - &.orgs_manage_projects - background-position: -599px -40px - &.permissions - background-position: -200px -40px - &.licenses - background-position: -240px -40px - &.tags - background-position: -280px -40px - &.history - background-position: -320px -40px - &.managers - background-position: -360px -40px - &.news - background-position: -400px -40px - &.badges - background-position: -891px -40px - .settings_description - width: 240px - padding-left: 10px - // module icon sprites - .module_icon - background-image: asset_url('icons/ohloh-icon-sprite.png') - background-repeat: no-repeat - height: 40px + color: #4a1f6e + &::after + color: #5A2A82 + transform: translateX(2px) + + .settings-icon-wrapper width: 40px - display: block - &.basics - background-position: 0 0 - &.code_locations - background-position: -40px 0px - &.aliases - background-position: -80px 0px - &.logo - background-position: -120px 0px - &.links - background-position: -160px 0px - &.orgs_manage_projects - background-position: -599px 0px - &.permissions - background-position: -200px 0px - &.licenses - background-position: -240px 0px - &.tags - background-position: -280px 0px - height: 36px - width: 31px - &.history - background-position: -320px 0px - &.managers - background-position: -360px 0px - width: 36px - &.news - background-position: -400px 0px - &.badges - background-position: -890px 0px + height: 40px + min-width: 40px + border-radius: 12px + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) + transition: transform 0.2s ease + + i.fa + color: #ffffff + font-size: 18px + line-height: 1 + + &.gradient-purple + background: linear-gradient(135deg, #a855f7, #6366f1) + &.gradient-blue + background: linear-gradient(135deg, #3b82f6, #06b6d4) + &.gradient-teal + background: linear-gradient(135deg, #14b8a6, #059669) + &.gradient-pink + background: linear-gradient(135deg, #ec4899, #e11d48) + &.gradient-orange + background: linear-gradient(135deg, #f97316, #d97706) + &.gradient-red + background: linear-gradient(135deg, #ef4444, #be123c) + &.gradient-green + background: linear-gradient(135deg, #22c55e, #0d9488) + &.gradient-yellow + background: linear-gradient(135deg, #eab308, #f97316) + &.gradient-violet + background: linear-gradient(135deg, #8b5cf6, #7e22ce) + &.gradient-sky + background: linear-gradient(135deg, #0ea5e9, #2563eb) + &.gradient-indigo + background: linear-gradient(135deg, #6366f1, #1d4ed8) + &.gradient-amber + background: linear-gradient(135deg, #f59e0b, #eab308) + + .settings-text + flex: 1 + min-width: 0 + + h4 + font-size: 14px !important + font-weight: 600 !important + color: #5A2A82 + margin: 0 !important + transition: color 0.2s ease + text-decoration: none + line-height: 1.3 !important + padding: 0 + a + color: inherit + text-decoration: none + + .settings_description + font-size: 12px + color: #6b7280 + line-height: 1.6 + margin: 4px 0 0 0 + display: block + font-style: normal #projects_similar_page .project_content_title @@ -245,7 +517,381 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ #links_index_page .links - width: 600px + width: 100% + max-width: 100% + +// Shared form-control mixin used by link-form and license-form +// Link new/edit form — input styles matching project basics +.link-form + .form-control + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + width: 100% !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + &::placeholder + color: #9ca3af !important + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #d1d5db !important + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &:focus, &:focus-visible + border-color: #ffb91a !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + + .actions + display: flex + gap: 8px + align-items: center + +// Tags index — tag chips styled as primary color +// .similar_tags_tagged.show_tags.tags uses 3-class specificity to beat tags.sass .tags .tag (2-class) when both !important +#current_tags .tag, +.similar_tags_tagged.show_tags.tags .tag + display: inline-flex + align-items: center + gap: 4px + padding: 12px 20px + border-radius: 9999px + font-size: 16px + font-weight: 500 + background-color: #5A2A82 !important + color: #ffffff !important + border: none !important + text-decoration: none + cursor: pointer + transition: background-color 0.2s ease, box-shadow 0.2s ease + margin: 2px 4px 2px 0 + + &:hover + background-color: #4a1f6e !important + box-shadow: 0 2px 6px rgba(90, 42, 130, 0.3) + text-decoration: none + + i.icon-remove + font-size: 11px + opacity: 0.7 + &:hover + opacity: 1 + + html.dark & + background-color: #ffb91a !important + color: #1D0631 !important + border: none !important + &:hover + background-color: #ffcc4d !important + box-shadow: 0 2px 6px rgba(255, 185, 26, 0.3) + +// Tags form input — matches project basics form-control style +#edit_tags + .form-control + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + width: 100% !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + &::placeholder + color: #9ca3af !important + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #d1d5db !important + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &:focus, &:focus-visible + border-color: #ffb91a !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + +// RSS subscriptions — mobile card view +.rss-cards-container + flex-direction: column + gap: 12px + + @media (max-width: 767px) + display: flex + +.rss-card-item + background: #ffffff + border-radius: 12px + border: 1px solid #e5e7eb + padding: 14px 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) + display: flex + flex-direction: column + gap: 8px + + html.dark & + background: #2D1548 + border-color: rgba(255, 255, 255, 0.08) + + .rss-card-url + font-size: 14px + word-break: break-all + + .rss-card-meta + display: flex + gap: 8px + font-size: 13px + color: #6b7280 + align-items: center + + html.dark & + color: #9ca3af + + .rss-card-label + font-weight: 600 + color: #374151 + html.dark & + color: #d1d5db + + .rss-card-actions + margin-top: 4px + +// RSS subscription form — matches project basics form-control style +.rss-form + .form-control + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + width: 100% !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + &::placeholder + color: #9ca3af !important + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #d1d5db !important + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &:focus, &:focus-visible + border-color: #ffb91a !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + +// Managers form textarea — matches project basics form-control style +.manager-form + .form-control + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + width: 100% !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + &::placeholder + color: #9ca3af !important + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #d1d5db !important + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &:focus, &:focus-visible + border-color: #ffb91a !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + +.links-index-page + max-width: 1200px + margin: 0 auto + padding: 24px 16px + + @media (max-width: 576px) + padding: 16px 12px + +.links-page-header + margin-bottom: 24px + padding-bottom: 12px + border-bottom: 2px solid #e9ecef + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.3) + +.links-page-title + font-size: 22px + font-weight: 700 + color: #1f2937 + margin: 0 + display: flex + align-items: center + flex-wrap: wrap + gap: 6px + + html.dark & + color: #f3f4f6 + + @media (max-width: 576px) + font-size: 18px + +.links-settings-link + color: #7c3aed + text-decoration: none + + &:hover + text-decoration: underline + color: #5b21b6 + + html.dark & + color: #a78bfa + + html.dark &:hover + color: #c4b5fd + +.links-title-sep + color: #9ca3af + font-weight: 400 + +.links-categories-grid + display: grid + grid-template-columns: repeat(2, 1fr) + gap: 20px + align-items: start + + @media (max-width: 767px) + grid-template-columns: 1fr + +.links-category-card + max-height: 340px + overflow-y: auto + margin-bottom: 0 !important + +.links-category-header + padding: 14px 16px 10px + border-bottom: 1px solid #f3f4f6 + + html.dark & + border-bottom-color: rgba(90, 42, 130, 0.2) + +.links-category-title + font-size: 15px + font-weight: 600 + color: #374151 + margin: 0 + + html.dark & + color: #e5e7eb + +.links-list + display: flex + flex-direction: column + padding: 8px 16px 4px + +.link-row + display: flex + align-items: center + gap: 12px + padding: 8px 0 + border-bottom: 1px solid #f3f4f6 + + html.dark & + border-color: rgba(255, 255, 255, 0.06) + + &:last-child + border-bottom: none + + .link-title + width: 150px + flex-shrink: 0 + font-size: 14px + font-weight: 500 + color: #374151 + overflow: hidden + text-overflow: ellipsis + white-space: nowrap + + html.dark & + color: #d1d5db + + .link-url + flex: 1 + min-width: 0 + overflow: hidden + text-overflow: ellipsis + white-space: nowrap + font-size: 13px + + h5 + margin: 0 + + a + color: #7c3aed + text-decoration: none + + &:hover + text-decoration: underline + + html.dark & + color: #a78bfa + + .link-actions + flex-shrink: 0 + +.link-add-row + padding: 10px 16px 14px + +.links-empty + display: block + font-size: 13px + font-style: italic + color: #9ca3af + padding: 10px 16px + + html.dark & + color: #6b7280 + +@media (max-width: 576px) + .link-row + flex-wrap: wrap + gap: 6px + + .link-title + width: 100% + white-space: normal + + .link-url + flex: 1 + min-width: 0 + + .link-actions + margin-left: auto .license-template display: none @@ -261,7 +907,7 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ padding-left: 10px padding-right: 10px .range-good, .range-poor - font-size: 1.2rem + font-size: 19px text-align: center #pss_bg, #pvs_bg @@ -318,8 +964,8 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ height: 250px #vulnerability_per_version - padding: 19px - .well + padding: 19px + .well, .oh-card background: white !important .title span @@ -356,7 +1002,7 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ #permitted @include licenses-permitted-color margin-top: -20px - .well + .oh-card padding-bottom: 0 padding-top: 0 height: 85% @@ -366,7 +1012,7 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ @include licenses-forbidden-color margin-top: -20px padding-left: 0 - .well + .oh-card padding-bottom: 0 padding-top: 0 height: 85% @@ -376,7 +1022,7 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ @include licenses-required-color margin-top: -5px padding-left: 0 - .well + .oh-card padding-bottom: 0 padding-top: 0 height: 91% @@ -440,10 +1086,10 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ .ctrl width: auto .title - padding-right: 1rem + padding-right: 16px #vulnerability-chart-divider - padding: 40px 4rem 20px 4rem !important + padding: 40px 64px 20px 64px !important .vulnerabilities-datatable tbody tr @@ -476,9 +1122,9 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ margin-right: auto #project_badges_page - margin-bottom: 2rem + margin-bottom: 32px .title - margin-bottom: 2rem + margin-bottom: 32px .no-left-padding padding-left: 0 .badge_table_container @@ -486,22 +1132,22 @@ $pai_levels: (('very_high',0), ('high', 1), ('moderate', 2), ('low', 3), ('very_ .table tbody tr td - padding-top: 1.5rem - padding-bottom: 1.5rem + padding-top: 24px + padding-bottom: 24px vertical-align: top !important .btn-mini.col-xs-12 - padding: 0.12rem 1.5rem + padding: 2px 24px width: 100% .url_update_btn color: green - font-size: 2.1rem + font-size: 34px cursor: pointer .edit_url_close_btn color: red - font-size: 2.1rem + font-size: 34px cursor: pointer #save_badge - margin-bottom: 0.5rem + margin-bottom: 8px .project_summary_container section @@ -569,3 +1215,1524 @@ dl.kpi margin: 0 30px .scan_data_title padding-left: 1em + +// Modern Card Layout Design - Figma Update +// SASS Variables for Light Mode +$primary-color: #5A2A82 +$primary-hover: rgba(90, 42, 130, 0.9) +$accent-color: #ffb91a +$bg-color: #f9fafb +$card-bg: #ffffff +$text-primary: #1f2937 +$text-secondary: #6b7280 +$border-color: rgba(229, 231, 235, 0.8) +$shadow-sm: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) +$shadow-md: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23) +$shadow-lg: 0 8px 16px rgba(0,0,0,0.16) + +// Dark Mode Variables +$dark-primary-color: #ffb91a +$dark-primary-hover: rgba(255, 185, 26, 0.9) +$dark-accent-color: #5A2A82 +$dark-bg-color: #1D0631 +$dark-card-bg: #2D1548 +$dark-text-primary: #ffffff +$dark-text-secondary: #d1d5db +$dark-border-color: rgba(255, 255, 255, 0.1) +$dark-shadow-sm: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) +$dark-shadow-md: 0 3px 6px rgba(0,0,0,0.32), 0 3px 6px rgba(0,0,0,0.46) +$dark-shadow-lg: 0 8px 16px rgba(0,0,0,0.48) + +// Modern Projects Index Page +#projects_index_page + background-color: $bg-color + min-height: 100vh + padding: 32px 0 + transition: background-color 0.3s ease, color 0.3s ease + html.dark & + background-color: $dark-bg-color + + .projects-container + max-width: 1280px + margin: 0 auto + padding: 0 16px + + // Header Section + .projects-header + display: flex + justify-content: space-between + align-items: center + margin-bottom: 24px + flex-wrap: wrap + gap: 16px + + h1 + font-size: 30px + font-weight: bold + color: $text-primary + margin: 0 + html.dark & + color: $dark-text-primary + + @media (min-width: 640px) + font-size: 36px + + .header-actions + display: flex + gap: 12px + align-items: center + flex-wrap: wrap + + .btn-browse-tags, + .btn-add-project + padding: 8px 16px + border-radius: 8px + font-weight: 600 + font-size: 14px + transition: all 0.2s ease + text-decoration: none + display: inline-flex + align-items: center + gap: 8px + white-space: nowrap + + i + font-size: 14px + flex-shrink: 0 + + .btn-browse-tags + background-color: $card-bg + border: 1px solid $border-color + color: $text-secondary + box-shadow: $shadow-sm + &:hover + border-color: $primary-color + html.dark & + background-color: $dark-card-bg + border-color: $dark-border-color + color: #ffffff !important + box-shadow: $dark-shadow-sm + &:hover + border-color: $dark-primary-color + + .btn-add-project + background-color: $primary-color + color: #ffffff + border: none + box-shadow: $shadow-md + &:hover + background-color: $primary-hover + html.dark & + background-color: $dark-primary-color + color: #1D0631 !important + box-shadow: $dark-shadow-md + &:hover + background-color: $dark-primary-hover + + +// Modern Project Card +.project-card + background-color: $card-bg + border-radius: 12px + border: 1px solid $border-color + padding: 20px 24px + margin-bottom: 16px + box-shadow: $shadow-sm + transition: all 0.3s ease + html.dark & + background-color: $dark-card-bg + border-color: $dark-border-color + box-shadow: $dark-shadow-sm + + &:hover + border-color: $primary-color + box-shadow: $shadow-md + html.dark & + border-color: $dark-primary-color + box-shadow: $dark-shadow-md + + .project-header + display: flex + justify-content: space-between + align-items: flex-start + margin-bottom: 16px + + .project-title-section + flex: 1 + + h2 + margin: 0 0 4px 0 + font-size: 18px + font-weight: 600 + + @media (min-width: 640px) + font-size: 20px + + a + color: $primary-color + text-decoration: none + &:hover + text-decoration: underline + html.dark & + color: $dark-primary-color !important + + .compare-section + .compare-checkbox + display: flex + align-items: center + gap: 6px + font-size: 14px + color: $text-secondary + cursor: pointer + transition: color 0.15s ease + + &:hover + color: $primary-color + + &.selected + color: $primary-color + font-weight: 500 + + i + color: $primary-color + + html.dark & + color: $dark-text-secondary + + &:hover + color: $dark-primary-color + + &.selected + color: $dark-primary-color + + i + color: $dark-primary-color + + .project-content + display: grid + grid-template-columns: 1fr auto + column-gap: 24px + row-gap: 0 + + @media (max-width: 768px) + grid-template-columns: 1fr + + .project-meta + grid-column: 1 + grid-row: 1 + display: flex + gap: 16px + font-size: 12px + color: $text-secondary + html.dark & + color: $dark-text-secondary + + @media (min-width: 640px) + font-size: 14px + + .claimed-by + span + color: $primary-color + font-weight: 500 + margin-left: 4px + + a + color: $primary-color + text-decoration: none + &:hover + text-decoration: underline + + html.dark & + color: $dark-primary-color !important + + .analyzed-date + font-style: italic + margin-left: auto + + > i + color: $primary-color !important + + abbr + color: inherit !important + + html.dark & + > i + color: $dark-primary-color !important + + .soft + color: $text-secondary + html.dark & + color: $dark-text-secondary + + .project-main + grid-column: 1 + grid-row: 2 + min-width: 300px + + .project-icon-desc + display: flex + gap: 12px + margin-bottom: 12px + + .project-description + flex: 1 + font-size: 14px + color: $text-secondary + line-height: 1.5 + html.dark & + color: $dark-text-secondary + + .read-more + color: $primary-color + font-weight: 500 + cursor: pointer + &:hover + text-decoration: underline + html.dark & + color: $dark-primary-color + + .project-lang-license + display: flex + gap: 24px + margin-bottom: 12px + font-size: 14px + color: $text-secondary + flex-wrap: wrap + html.dark & + color: $dark-text-secondary + + .language + a + color: $primary-color + font-weight: 500 + text-decoration: none + &:hover + text-decoration: underline + html.dark & + color: $dark-primary-color !important + + .licenses + .license-label + font-weight: 500 + color: $text-primary + html.dark & + color: $dark-text-primary + + .project-stats + grid-column: 2 + grid-row: 1 / 3 + min-width: 280px + border-left: 1px solid $border-color + padding-left: 24px + html.dark & + border-color: $dark-border-color + + @media (max-width: 768px) + grid-column: 1 + grid-row: auto + border-left: none + border-top: 1px solid $border-color + padding-left: 0 + padding-top: 16px + margin-bottom: 16px + + .stats-grid + display: grid + grid-template-columns: 1fr auto + gap: 10px 20px + align-items: center + + .stat-item + display: flex + align-items: center + gap: 10px + + .stat-value + font-size: 16px + font-weight: bold + color: $primary-color + text-align: left + html.dark & + color: $dark-primary-color + + @media (min-width: 640px) + font-size: 18px + + @media (min-width: 768px) + font-size: 20px + + .stat-label + font-size: 12px + color: $text-secondary + html.dark & + color: $dark-text-secondary + + @media (min-width: 640px) + font-size: 14px + + .activity-indicator + grid-row: span 2 + display: flex + flex-direction: column + align-items: center + gap: 4px + + .activity-icon + position: relative + width: 40px + height: 40px + display: flex + align-items: center + justify-content: center + + a[class^="twentyfive_project_activity_level_"] + position: static !important + margin-left: 0 !important + top: auto !important + display: block + transform: scale(1.6) + + .activity-label + font-size: 11px + color: $text-secondary + text-align: center + line-height: 1.2 + max-width: 80px + html.dark & + color: $dark-text-secondary + + .rating-section + display: flex + align-items: center + gap: 6px + + .stars + display: flex + gap: 2px + + .star + width: 14px + height: 14px + + &.filled + color: #ffb91a + + &.half + color: #ffb91a + + &.empty + color: #d1d5db + html.dark & + color: #4b5563 + + .review-count + font-size: 12px + color: $primary-color + font-weight: 500 + cursor: pointer + &:hover + text-decoration: underline + html.dark & + color: $dark-primary-color + + .use-this-button + display: flex + justify-content: center + align-items: center + + .project-tags + display: flex + flex-wrap: wrap + gap: 6px + align-items: center + + .tags-label + font-size: 12px + color: $text-secondary + margin-right: 4px + html.dark & + color: $dark-text-secondary + + .tag + padding: 2px 10px + border-radius: 9999px + font-size: 11px + font-weight: 500 + background-color: rgba(255, 185, 26, 0.2) + color: #1D0631 + transition: background-color 0.2s ease + cursor: pointer + text-decoration: none + &:hover + background-color: rgba(255, 185, 26, 0.3) + html.dark & + background-color: #ffb91a + color: #1D0631 !important + + .more-tags + color: $primary-color + font-size: 12px + font-weight: 500 + cursor: pointer + &:hover + text-decoration: underline + html.dark & + color: $dark-primary-color + +// Responsive adjustments +@media (max-width: 1024px) + .project-card + padding: 16px + + .project-header + .project-meta + font-size: 12px + +@media (max-width: 640px) + #projects_index_page + .projects-header + .header-actions + width: 100% + justify-content: flex-start + + .btn-browse-tags, + .btn-add-project + flex: 1 + min-width: 0 + justify-content: center + + .project-card + padding: 16px + + .project-header + .project-meta + font-size: 12px + + .project-content + .project-main + .project-icon-desc + .project-icon + width: 48px + height: 48px + font-size: 24px + +// ============================================================================ +// Settings Form Pages — Responsive (mobile & tablet) +// ============================================================================ + +// Collapse fixed left margins on small screens +@media (max-width: 767px) + #accounts_edits + .margin_left_15, + h2.margin_left_15 + margin-left: 0 !important + + .oh-card.margin_left_15 + margin-left: 0 !important + margin-right: 0 !important + border-radius: 12px + + // Collapse Bootstrap col-md-* columns to full width on mobile + #account-edit-form + .col-md-5, + .col-md-5.col-md-push-1, + .col-md-6, + .col-md-7 + width: 100% !important + float: none !important + left: 0 !important + margin-left: 0 !important + padding-left: 12px !important + padding-right: 12px !important + + .control-group, + .form-group + margin-bottom: 16px + + .form-control + width: 100% !important + box-sizing: border-box !important + + // Alter password form + .oh-card.col-md-7 + &#alter-password-form + width: 100% !important + float: none !important + margin-left: 0 !important + padding-right: 12px !important + + .alter-password-heading + font-size: 18px !important + margin-left: 0 !important + + .alter-password-about + margin-top: 16px + .col-md-6 + width: 100% !important + float: none !important + padding-left: 0 + + // Privacy form + #privacy_edit_page + .col-md-6, + .col-md-5 + width: 100% !important + float: none !important + padding-left: 0 + padding-right: 0 + + // API keys form + #api-key-form + margin-left: 0 !important + margin-right: 0 !important + &.col-md-11 + width: 100% !important + float: none !important + + // Ensure all form controls are full-width on mobile + .control-group, + .form-group + .controls, + .control + input[type="text"], + input[type="email"], + input[type="password"], + input[type="url"], + select, + textarea + width: 100% !important + box-sizing: border-box !important + + // About card columns + .about-account-basics-card, + .alter-password-about + .col-md-5, + .col-md-6 + width: 100% !important + float: none !important + padding-left: 0 + + // Settings page header description dedent on mobile + .settings-page-header + .settings-page-description + margin-left: 0 + +// Tablet: let col-md columns be 50/50 rather than full width +@media (min-width: 768px) and (max-width: 991px) + #account-edit-form + .col-md-5 + width: 50% !important + .col-md-5.col-md-push-1 + width: 45% !important + left: 4% !important + +// ============================================================================ +// Project New page +// ============================================================================ + +#project-new-page + width: 90% + + .oh-card + padding: 28px + + .project-new-help + p + font-size: 14px + color: #4b5563 + margin-bottom: 8px + html.dark & + color: #9ca3af + + .form-control + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + -webkit-border-radius: 16px !important + -moz-border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + width: 100% !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + &::placeholder + color: #9ca3af !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important + border-color: #d1d5db !important + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(90, 42, 130, 0.1) !important + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &::placeholder + color: #6b7280 !important + &:focus, &:focus-visible + border-color: #ffb91a !important + background: #1D0631 !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + + .project-new-examples + margin-top: 16px + padding: 14px 16px + background: #f9fafb + border: 1px solid #e5e7eb + border-radius: 12px + max-width: 100% + overflow: hidden + html.dark & + background: #2D1548 + border-color: #5A2A82 + + .project-new-examples-label + font-size: 12px + font-weight: 600 + text-transform: uppercase + letter-spacing: 0.05em + color: #6b7280 + margin-bottom: 8px + html.dark & + color: #9ca3af + + .project-new-example-item + margin-bottom: 6px + &:last-child + margin-bottom: 0 + + code + display: inline + font-size: 12px + color: #5A2A82 + word-break: break-all + overflow-wrap: break-word + white-space: normal + html.dark & + color: #ffb91a + + @media (max-width: 767px) + padding: 12px + code + font-size: 11px + + @media (max-width: 767px) + width: 100% !important + + &.margin_left_30, .project-new-title + margin-left: 0 !important + padding-left: 15px + + .project-new-row + margin-left: 0 !important + + .oh-card + padding: 16px + border-radius: 12px + + .project-new-examples code + font-size: 11px + +// Project Edit / Basics Form +// ============================================================================ + +#project-edit-page + width: 90% + .edit_project + width: 90% + margin: 0 auto + + // Input fields — matches account basics form-control style + .form-control + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + -webkit-border-radius: 16px !important + -moz-border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + width: 100% !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + &::placeholder + color: #9ca3af !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #d1d5db !important + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + background: #ffffff !important + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &::placeholder + color: #6b7280 !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #6b7280 !important + &:focus, &:focus-visible + border-color: #ffb91a !important + background: #1D0631 !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + outline: none !important + + textarea.form-control.edit-description + height: 150px !important + resize: vertical + + // URL field — prepend add-on + input side by side + // display:flex !important overrides Bootstrap .input-prepend { display: inline-block } + .project-url-prepend + display: flex !important + align-items: stretch !important + width: 100% !important + + .add-on + display: inline-flex !important + align-items: center + padding: 10px 12px + background: #f3f4f6 + border: 2px solid #e5e7eb + border-right: none + border-radius: 16px 0 0 16px + color: #6b7280 + font-size: 13px + white-space: nowrap + flex-shrink: 0 + html.dark & + background: #2D1548 + border-color: #5A2A82 + color: #9ca3af + + .form-control + border-radius: 0 16px 16px 0 !important + width: auto !important + flex: 1 1 0 !important + min-width: 0 + + // Mobile responsive + @media (max-width: 767px) + width: 100% !important + + .check-forge-title + margin-left: 0 !important + padding-left: 15px + font-size: 20px + + .check-forge-row + margin-left: 0 !important + + .oh-card + padding: 16px + border-radius: 12px + + .check-forge-fieldset + margin-left: 0 !important + padding-left: 0 + padding-right: 0 + + .control-label + font-size: 13px + + // Vanity URL — stack add-on above input on very small screens + .project-url-prepend + flex-wrap: wrap + + .add-on + border-right: 2px solid #e5e7eb + border-radius: 16px 16px 0 0 + width: 100% + html.dark & + border-right-color: #5A2A82 + + .form-control + border-radius: 0 0 16px 16px !important + width: 100% !important + + // License remove button + .license-remove-btn-wrap + float: right + text-align: right + padding: 0 + margin: 0 + + .actions + .btn + width: 100% + +// About Project Basics — collapsible card (mirrors .about-code-locations-card) +.source-code-fields + display: flex + gap: 8px + align-items: center + flex-wrap: nowrap + + select.form-control + flex: 0 0 120px + width: 120px !important + + input.form-control:not([id='repository_type']) + flex: 1 1 0 + min-width: 0 + + @media (max-width: 767px) + flex-wrap: wrap + gap: 6px + + select.form-control, + input.form-control + flex: 1 1 100% !important + width: 100% !important + +.about-project-basics-card + background: #ffffff + border-radius: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + overflow: hidden + margin-top: 24px + margin-bottom: 24px + + html.dark & + background: #2D1548 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + .card-header + display: flex + align-items: center + justify-content: space-between + padding: 16px 24px + cursor: pointer + transition: background-color 0.2s + + &:hover + background-color: #f9fafb + + html.dark & + background-color: rgba(255, 255, 255, 0.05) + + .card-title + margin: 0 + font-size: 16px + font-weight: 600 + color: #5A2A82 + flex: 1 + + html.dark & + color: #ffb91a + + .soft + margin-right: 4px + + .expand-toggle + background: none + border: none + padding: 8px + cursor: pointer + color: #6b7280 + transition: transform 0.2s + margin-left: 12px + + html.dark & + color: #9ca3af + + i + font-size: 16px + + .card-content + display: none + padding: 0 24px 16px + + &.expanded + .card-header + .expand-toggle + transform: rotate(180deg) + + .card-content + display: block + + .about-content-wrapper + display: flex + gap: 40px + + @media (max-width: 768px) + flex-direction: column + gap: 24px + + ul + list-style: disc + padding-left: 20px + + li + margin-bottom: 8px + line-height: 1.6 + +// Project edit page — responsive +@media (max-width: 767px) + #project-edit-page + .edit_project + width: 100% !important + + .oh-card.col-md-8 + width: 100% !important + float: none !important + padding-left: 12px !important + padding-right: 12px !important + + .project-url-prepend + flex-wrap: wrap !important + + .add-on + border-radius: 16px 16px 0 0 !important + border-right: 2px solid #e5e7eb !important + border-bottom: none !important + width: 100% !important + justify-content: center + padding: 0 !important + + .form-control + border-radius: 0 0 16px 16px !important + width: 100% !important + flex: none !important + + .about-project-basics-card + .about-content-wrapper + .col-md-6 + width: 100% !important + float: none !important + padding-left: 0 + +// ── Users page ──────────────────────────────────────────────────────────────── +.pu-page + padding: 24px 0 + +.pu-page__title + display: flex + align-items: center + gap: 12px + margin-bottom: 24px + + h2 + font-size: 26px + font-weight: 700 + color: #111827 + margin: 0 + + html.dark & + color: #ffffff + +.pu-page__title-icon + width: 36px + height: 36px + border-radius: 10px + background: linear-gradient(135deg, #5A2A82, #4338ca) + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + box-shadow: 0 2px 6px rgba(90,42,130,0.4) + + .fa + color: #ffffff + font-size: 16px + +.pu-user-list + display: flex + flex-direction: column + gap: 16px + margin-bottom: 24px + +.pu-user-card + background-color: #ffffff + border-radius: 16px + border: 1px solid #e5e7eb + box-shadow: 0 1px 3px rgba(0,0,0,0.08) + padding: 20px + display: flex + align-items: flex-start + justify-content: space-between + gap: 20px + transition: box-shadow 0.2s ease + + &:hover + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23) + + html.dark & + background-color: #2D1548 + border-color: rgba(90,42,130,0.4) + box-shadow: 0 2px 8px rgba(0,0,0,0.3) + + &:hover + box-shadow: 0 3px 6px rgba(0,0,0,0.32), 0 3px 6px rgba(0,0,0,0.46) + + &__left + display: flex + align-items: flex-start + gap: 16px + flex: 1 + min-width: 0 + + &__avatar-link + flex-shrink: 0 + + img + width: 56px + height: 56px + border-radius: 8px + object-fit: cover + display: block + + &__info + flex: 1 + min-width: 0 + + &__name + font-size: 16px + font-weight: 700 + margin: 0 0 4px + + a + color: #5A2A82 + text-decoration: none + + &:hover + text-decoration: underline + + html.dark & + color: #ffb91a + + &__stacks + font-size: 13px + color: #6b7280 + margin-bottom: 12px + + html.dark & + color: #9ca3af + + &__contributions + margin-top: 4px + + &__contributions-label + font-size: 11px + font-weight: 700 + color: #374151 + text-transform: uppercase + letter-spacing: 0.05em + margin: 0 0 6px + + html.dark & + color: #d1d5db + + &__right + display: flex + flex-direction: column + align-items: flex-end + gap: 6px + padding-top: 4px + flex-shrink: 0 + + &__kudo-label + font-size: 11px + font-weight: 700 + color: #374151 + text-transform: uppercase + letter-spacing: 0.05em + margin: 0 + + html.dark & + color: #d1d5db + +@media (max-width: 640px) + .pu-user-card + flex-direction: column + gap: 16px + + &__right + flex-direction: row + align-items: center + gap: 8px + +// ── Project Badges page ─────────────────────────────────────────────────────── +#project_badges_page + max-width: 1200px + margin: 0 auto + padding: 24px 16px + + @media (max-width: 576px) + padding: 16px 12px + + .pb-page__header + margin-bottom: 24px + + .pb-page__title + display: flex + align-items: flex-start + gap: 12px + + .pb-page__title-icon + width: 36px + height: 36px + border-radius: 10px + background: linear-gradient(135deg, #f59e0b, #ca8a04) + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + box-shadow: 0 2px 6px rgba(245,158,11,0.4) + + .fa + color: #ffffff + font-size: 16px + + .pb-page__title-text + h2 + font-size: 22px + font-weight: 700 + color: #111827 + margin: 0 0 4px + display: flex + flex-wrap: wrap + align-items: baseline + gap: 4px + + @media (max-width: 576px) + font-size: 18px + + a + color: #7c3aed + text-decoration: none + + &:hover + color: #5b21b6 + text-decoration: underline + + html.dark & + color: #f3f4f6 + + a + color: #a78bfa + + &:hover + color: #c4b5fd + + .pb-page__subtitle + font-size: 13px + color: #6b7280 + margin: 0 + + html.dark & + color: #9ca3af + + // Custom table scroll wrapper — Bootstrap's .table-responsive is NOT used here + // because overflow-x:auto clamps overflow-y to auto (per CSS spec), which + // clips absolutely-positioned chosen dropdowns. On mobile, column widths + // naturally collapse; horizontal scroll is handled only when truly needed. + .pb-table-scroll + width: 100% + overflow-x: auto + -webkit-overflow-scrolling: touch + + // Card wrapper + .pb-table-card + background-color: #ffffff + border-radius: 16px + border: 1px solid #e5e7eb + box-shadow: 0 1px 3px rgba(0,0,0,0.08) + overflow: visible + margin-bottom: 20px + + html.dark & + background-color: #2D1548 + border-color: rgba(90,42,130,0.4) + box-shadow: 0 2px 8px rgba(0,0,0,0.3) + + // Round top corners via th — no overflow:hidden needed on the table + .pb-table__head th:first-child + border-radius: 16px 0 0 0 + + .pb-table__head th:last-child + border-radius: 0 16px 0 0 + + // Table + .pb-table + margin: 0 + width: 100% + border-collapse: collapse + + &.table + margin-bottom: 0 + + .pb-table__head + th + background-color: #f9fafb + padding: 12px 16px + font-size: 11px + font-weight: 700 + text-transform: uppercase + letter-spacing: 0.05em + color: #6b7280 + border-bottom: 1px solid #e5e7eb + border-top: none + white-space: nowrap + + html.dark & + background-color: #1D0631 + color: #9ca3af + border-bottom-color: rgba(90,42,130,0.3) + + .pb-table__body + tr + border-bottom: 1px solid #f3f4f6 + + &:last-child + border-bottom: none + + html.dark & + border-bottom-color: rgba(90,42,130,0.2) + + td + padding: 14px 16px + vertical-align: middle + font-size: 13px + color: #374151 + + html.dark & + color: #d1d5db + + // Inputs and selects inside table + .pb-table select, + .pb-table input[type="text"], + .pb-table input[type="url"], + .pb-table .badge_url_holder, + .pb-table .edit_url_field, + .pb-table .dirty_url_field + width: 100% + padding: 7px 10px + font-size: 13px + border: 1px solid #e5e7eb + border-radius: 8px + background-color: #f9fafb + color: #111827 + transition: border-color 0.2s ease, box-shadow 0.2s ease + box-sizing: border-box + + &:focus, &:focus-visible + outline: none + border-color: #5A2A82 + box-shadow: 0 0 0 3px rgba(90,42,130,0.1) + background-color: #ffffff + + html.dark & + background-color: #1D0631 + border-color: rgba(90,42,130,0.5) + color: #ffffff + + &:focus, &:focus-visible + border-color: #ffb91a + box-shadow: 0 0 0 3px rgba(255,185,26,0.1) + + // Remove button inside table + .pb-table .btn-danger + background-image: none + border-radius: 8px + font-size: 12px + font-weight: 600 + padding: 5px 10px + + // Action buttons row + .pb-actions + display: flex + align-items: center + gap: 10px + flex-wrap: wrap + margin-top: 16px + + .pb-actions__right + display: flex + align-items: center + gap: 8px + margin-left: auto + + .pb-btn-new + display: inline-flex + align-items: center + gap: 6px + padding: 8px 18px + border-radius: 8px + border: 2px solid #5A2A82 + background: transparent + color: #5A2A82 + font-size: 14px + font-weight: 600 + cursor: pointer + transition: all 0.2s ease + + &:hover + background-color: #5A2A82 + color: #ffffff + + html.dark & + border-color: #ffb91a + color: #ffb91a + + &:hover + background-color: #ffb91a + color: #1D0631 + + .pb-btn-save + display: inline-flex + align-items: center + padding: 8px 24px + border-radius: 8px + border: none + background: linear-gradient(135deg, #5A2A82, #7B3FB5) + color: #ffffff + font-size: 14px + font-weight: 600 + cursor: pointer + transition: opacity 0.2s ease + + &:hover + opacity: 0.9 + + .pb-btn-cancel + display: inline-flex + align-items: center + padding: 8px 24px + border-radius: 8px + border: none + background-color: #e5e7eb + color: #374151 + font-size: 14px + font-weight: 600 + cursor: pointer + transition: background-color 0.2s ease + + &:hover + background-color: #d1d5db + + html.dark & + background-color: #1D0631 + color: #d1d5db + + &:hover + background-color: #180529 + + @media (max-width: 640px) + .pb-actions + flex-direction: column + align-items: stretch + + .pb-actions__right + margin-left: 0 + width: 100% + + .pb-btn-save, + .pb-btn-cancel + flex: 1 + justify-content: center + + // ── Chosen dropdown fix ─────────────────────────────────────────────────── + // Do NOT set position:relative on td — that can create stacking-context traps + // in some browsers. .chosen-container is the positioned ancestor for the drop. + .pb-table .chosen-container + width: 100% !important + position: relative + + // Create a high stacking context only when the dropdown is open. + .pb-table .chosen-container.chosen-container-active + z-index: 9999 + + // Explicitly reveal the drop when open — chosen.css hides it via clip-path + // inset(100%,100%); override with higher specificity + !important to guarantee + // the drop is visible regardless of any ancestor overflow or stacking context. + .pb-table .chosen-container.chosen-with-drop .chosen-drop + clip: auto !important + -webkit-clip-path: none !important + clip-path: none !important + display: block !important + + .pb-table .chosen-container-single .chosen-single + height: 34px + line-height: 34px + padding: 0 0 0 10px + background: #f9fafb !important + background-image: none !important + border: 1px solid #e5e7eb !important + border-radius: 8px !important + box-shadow: none !important + color: #111827 + font-size: 13px + + div b + background-image: none !important + + &:before + content: '\f078' + font-family: FontAwesome + font-size: 11px + color: #6b7280 + position: relative + top: 0 + + .pb-table .chosen-container-active.chosen-with-drop .chosen-single, + .pb-table .chosen-container-active > .chosen-single + border-color: #5A2A82 !important + box-shadow: 0 0 0 3px rgba(90,42,130,0.1) !important + background: #ffffff !important + + .pb-table .chosen-drop + border: 1px solid #e5e7eb !important + border-top: none !important + border-radius: 0 0 8px 8px !important + box-shadow: 0 4px 6px rgba(0,0,0,0.08) !important + background: #ffffff !important + z-index: 9999 !important + position: absolute !important + + .pb-table .chosen-results li + font-size: 13px + color: #111827 !important + padding: 7px 12px + background: #ffffff !important + + &.highlighted + background: #5A2A82 !important + color: #ffffff !important + + &.no-results + background: #f9fafb !important + color: #6b7280 !important + + html.dark #project_badges_page + .pb-table .chosen-container-single .chosen-single + background: #1D0631 !important + border-color: rgba(90,42,130,0.5) !important + color: #ffffff + + .pb-table .chosen-container-active.chosen-with-drop .chosen-single, + .pb-table .chosen-container-active > .chosen-single + border-color: #ffb91a !important + box-shadow: 0 0 0 3px rgba(255,185,26,0.1) !important + background: #1D0631 !important + + .pb-table .chosen-drop + background: #1D0631 !important + border-color: rgba(90,42,130,0.5) !important + + .pb-table .chosen-results li + color: #ffffff + + &.highlighted + background: #ffb91a !important + color: #1D0631 !important diff --git a/app/assets/stylesheets/rating.sass b/app/assets/stylesheets/rating.sass index 69e6e3c0d..633a6f84e 100644 --- a/app/assets/stylesheets/rating.sass +++ b/app/assets/stylesheets/rating.sass @@ -60,3 +60,489 @@ span.score_text .review_content :padding-top 10px :padding-right 10px + +// Modern review card design +.oh-review-section-title + font-size: 18px + font-weight: 700 + color: #111827 + display: flex + align-items: center + gap: 8px + margin-bottom: 16px + + .fa + color: #5A2A82 + font-size: 16px + + html.dark & + color: #ffffff + + .fa + color: #ffb91a + +.oh-review-card + background-color: #ffffff + border-radius: 16px + border: 1px solid #e5e7eb + box-shadow: 0 1px 3px rgba(0,0,0,0.08) + overflow: hidden + + html.dark & + background-color: #2D1548 + border-color: rgba(90,42,130,0.4) + box-shadow: 0 2px 8px rgba(0,0,0,0.3) + + &__body + padding: 20px + + &__footer + display: flex + flex-wrap: wrap + align-items: center + gap: 8px + padding: 16px 20px + border-top: 1px solid #e5e7eb + + html.dark & + border-top-color: rgba(90,42,130,0.3) + +.oh-review-author-row + display: flex + align-items: flex-start + gap: 16px + margin-bottom: 16px + +.oh-review-avatar + width: 48px + height: 48px + border-radius: 50% + background: linear-gradient(135deg, #5A2A82, #7B3FB5) + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + + span + color: #ffffff + font-weight: 700 + font-size: 18px + line-height: 1 + +.oh-review-meta + flex: 1 + min-width: 0 + + &__header + display: flex + align-items: flex-start + justify-content: space-between + gap: 16px + margin-bottom: 8px + + &__left + flex: 1 + min-width: 0 + + &__right + flex-shrink: 0 + +.oh-review-author + font-weight: 600 + font-size: 14px + color: #111827 + margin: 0 0 4px + + html.dark & + color: #ffffff + + &__says + font-weight: 400 + color: #6b7280 + + html.dark & + color: #d1d5db + +.oh-review-title-row + display: flex + align-items: center + gap: 8px + margin-top: 4px + +.oh-review-title + font-size: 13px + font-weight: 600 + color: #059669 + margin: 0 + + html.dark & + color: #34d399 + +.oh-review-timestamp + display: flex + align-items: center + gap: 6px + font-size: 12px + color: #6b7280 + margin-bottom: 12px + + .fa + font-size: 12px + + html.dark & + color: #9ca3af + +.oh-review-comment + font-size: 14px + color: #374151 + line-height: 1.6 + + p + margin: 0 + + html.dark & + color: #d1d5db + +.oh-review-card__footer + .btn-info + background-color: #5A2A82 + background-image: none + border-color: #5A2A82 + color: #ffffff + border-radius: 8px + padding: 6px 14px + font-size: 13px + font-weight: 600 + transition: background-color 0.2s ease + + &:hover, &:focus, &:active + background-color: #4a1f6e + background-image: none + border-color: #4a1f6e + color: #ffffff + + .btn-danger + background-image: none + border-radius: 8px + padding: 6px 14px + font-size: 13px + font-weight: 600 + margin-left: 0 + + &:hover, &:focus, &:active + background-image: none + +// ── Review form inputs (new/edit) ───────────────────────────────────────────── +.new_review, +.edit_review + .form-control + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0,0,0,0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + width: 100% !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + + &::placeholder + color: #9ca3af !important + opacity: 1 + + &:hover + box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06) !important + border-color: #d1d5db !important + + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(90,42,130,0.1) !important + background: #ffffff !important + + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + + &::placeholder + color: #6b7280 !important + opacity: 1 + + &:hover + border-color: #6b7280 !important + + &:focus, &:focus-visible + border-color: #ffb91a !important + background: #1D0631 !important + box-shadow: 0 0 0 4px rgba(255,185,26,0.1) !important + + textarea.form-control + height: 150px !important + resize: vertical + + .control-label + font-weight: 600 + font-size: 14px + color: #374151 + margin-bottom: 6px + display: block + + html.dark & + color: #d1d5db + + .controls + margin-top: 4px + + .actions + display: flex + align-items: center + gap: 8px + margin-top: 16px + + .btn, + input.btn + height: 40px !important + padding: 0 20px !important + line-height: 40px !important + font-size: 14px !important + font-weight: 600 !important + border-radius: 8px !important + border-width: 1px !important + box-sizing: border-box !important + display: inline-flex !important + align-items: center !important + justify-content: center !important + background-image: none !important + text-shadow: none !important + box-shadow: none !important + +// ── Page layout ────────────────────────────────────────────────────────────── +.rr-page + padding: 24px 0 + +.rr-page__title + display: flex + align-items: center + gap: 12px + margin-bottom: 24px + + h2 + font-size: 26px + font-weight: 700 + color: #111827 + margin: 0 + + html.dark & + color: #ffffff + +.rr-page__title-icon + width: 36px + height: 36px + border-radius: 10px + background: linear-gradient(135deg, #ffb91a, #f97316) + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + box-shadow: 0 2px 6px rgba(255,185,26,0.4) + + .fa + color: #ffffff + font-size: 16px + +// ── Community Rating card ───────────────────────────────────────────────────── +.rr-community-card + background-color: #ffffff + border-radius: 16px + border: 1px solid #e5e7eb + box-shadow: 0 1px 3px rgba(0,0,0,0.08) + padding: 24px + margin-bottom: 24px + + html.dark & + background-color: #2D1548 + border-color: rgba(90,42,130,0.4) + box-shadow: 0 2px 8px rgba(0,0,0,0.3) + + &__title + font-size: 17px + font-weight: 700 + color: #111827 + margin: 0 0 20px + + html.dark & + color: #ffffff + + &__grid + display: grid + grid-template-columns: repeat(4, 1fr) + gap: 24px + + &__stat + text-align: center + + &__stars + display: flex + justify-content: center + margin-bottom: 6px + + &__value + font-size: 28px + font-weight: 700 + color: #5A2A82 + line-height: 1.2 + margin-bottom: 4px + + html.dark & + color: #ffb91a + + &__label + font-size: 12px + color: #6b7280 + margin: 0 + + html.dark & + color: #9ca3af + +// ── My Review section ───────────────────────────────────────────────────────── +.rr-my-review-section + margin-bottom: 32px + +// ── Reviews grid (2 col) ────────────────────────────────────────────────────── +.rr-section-title + display: flex + align-items: center + gap: 10px + font-size: 18px + font-weight: 700 + color: #111827 + margin-bottom: 16px + + html.dark & + color: #ffffff + + a + color: inherit + text-decoration: none + + &:hover + color: #5A2A82 + + html.dark & + color: #ffb91a + +.rr-section-title__icon + width: 32px + height: 32px + border-radius: 8px + background: linear-gradient(135deg, #5A2A82, #7B3FB5) + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + + .fa + color: #ffffff + font-size: 14px + + &--green + background: linear-gradient(135deg, #059669, #10b981) + +.rr-reviews-grid + display: grid + grid-template-columns: 1fr 1fr + gap: 24px + margin-bottom: 8px + +.rr-see-all + margin-top: 16px + + .btn-primary + border-radius: 8px + background-image: none + font-weight: 600 + +.rr-no-reviews + color: #6b7280 + font-style: italic + + html.dark & + color: #9ca3af + +// ── Add Review card ─────────────────────────────────────────────────────────── +.rr-add-review + display: flex + align-items: flex-start + gap: 16px + + &__icon + width: 40px + height: 40px + border-radius: 50% + background: linear-gradient(135deg, #5A2A82, #7B3FB5) + display: flex + align-items: center + justify-content: center + flex-shrink: 0 + + .fa + color: #ffffff + font-size: 16px + + &__content + flex: 1 + + &__message + font-size: 14px + color: #374151 + margin: 0 0 8px + + html.dark & + color: #d1d5db + + &__meta + display: flex + align-items: center + gap: 8px + font-size: 13px + +// ── Helpful section ─────────────────────────────────────────────────────────── +.oh-review-helpful + margin-top: 12px + padding-top: 10px + border-top: 1px solid #f3f4f6 + font-size: 12px + color: #6b7280 + display: flex + align-items: center + gap: 12px + flex-wrap: wrap + + html.dark & + border-top-color: rgba(90,42,130,0.2) + color: #9ca3af + +// ── Responsive ─────────────────────────────────────────────────────────────── +@media (max-width: 768px) + .rr-community-card__grid + grid-template-columns: repeat(2, 1fr) + + .rr-reviews-grid + grid-template-columns: 1fr + +@media (max-width: 640px) + .oh-review-meta__header + flex-direction: column + gap: 8px + + .oh-review-meta__right + display: none + + .rr-community-card__grid + grid-template-columns: repeat(2, 1fr) + gap: 16px + + .rr-community-card__value + font-size: 22px diff --git a/app/assets/stylesheets/rest_in_place.sass b/app/assets/stylesheets/rest_in_place.sass index 4c752b97e..e39c23b90 100644 --- a/app/assets/stylesheets/rest_in_place.sass +++ b/app/assets/stylesheets/rest_in_place.sass @@ -5,9 +5,13 @@ button :margin-top -7px a.rest_in_place_helper - :color #999 + :color #595959 :cursor pointer :border none :font-size 9pt &:hover :color #000 + html.dark & + color: #d1d5db + &:hover + color: #ffffff diff --git a/app/assets/stylesheets/search-dingus.sass b/app/assets/stylesheets/search-dingus.sass index f75db9c1d..5f5e6898c 100644 --- a/app/assets/stylesheets/search-dingus.sass +++ b/app/assets/stylesheets/search-dingus.sass @@ -1,97 +1,546 @@ -#search-dingus - @include site-well-color - padding: 7px 0px - line-height: 29px - min-height: 33px - margin-bottom: 1em +// Modern Header Search Form +.header-search-form + display: flex + align-items: stretch + flex: 1 + max-width: 400px + height: 40px + + @media (max-width: 640px) + order: 1 + max-width: 100% + flex-basis: 100% + + .header-search-dropdown + position: relative + display: flex + + .search-filter-btn + display: flex + align-items: center + gap: 6px + height: 40px + padding: 0 12px + background-color: #f9fafb + border: 1px solid #d1d5db + border-right: none + border-radius: 8px 0 0 8px + color: #1f2937 + font-size: 14px + font-weight: 500 + white-space: nowrap + transition: all 0.2s ease + cursor: pointer + box-sizing: border-box + line-height: normal + + &:hover + background-color: #f3f4f6 + + html.dark & + background-color: #1D0631 + border-color: #5A2A82 + color: #ffffff + + &:hover + background-color: rgba(29, 6, 49, 0.8) + + .selection + display: inline-block + + i + font-size: 12px + transition: transform 0.2s ease + + .search-dropdown-menu + width: 160px + margin-top: 4px + padding: 0 + background-color: #ffffff + border: 1px solid #d1d5db + border-radius: 8px + overflow: hidden + z-index: 1000 + // Material Design elevation shadow (level 3) + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23) + + html.dark & + background-color: #1D0631 + border-color: #5A2A82 + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.38), 0 6px 6px rgba(0, 0, 0, 0.46) + + .dropdown-item + display: block + width: 100% + padding: 8px 16px + color: #111827 + font-size: 14px + font-weight: 400 + text-align: left + text-decoration: none + background: transparent + border: none + box-sizing: border-box + transition: background 0.15s ease, color 0.15s ease + cursor: pointer + + &:hover + background: #f3f4f6 + + &.active + background-color: #5A2A82 !important + color: #ffffff + font-weight: 400 + + &:hover + background-color: #5A2A82 + + html.dark & + color: #ffffff + + &:hover + background: #374151 + + &.active + background-color: #ffb91a !important + color: #5A2A82 + font-weight: 400 + + &:hover + background-color: #ffb91a + + .header-search-input + flex: 1 + height: 40px + padding: 0 12px + background-color: #ffffff + border: 1px solid #d1d5db + color: #1f2937 + font-size: 14px + outline: none + border-right: none + box-shadow: none + min-width: 0 + transition: border-color 0.2s ease + box-sizing: border-box + line-height: normal + + &::placeholder + color: #6b7280 + + &:focus + outline: none + border-color: #5A2A82 + box-shadow: none + + html.dark & + border-color: #ffb91a !important + + html.dark & + background-color: #1D0631 + border-color: #5A2A82 !important + color: #ffffff + + &::placeholder + color: #d1d5db + + .header-search-btn + height: 40px + padding: 0 16px + background-color: #5A2A82 + border: 1px solid #5A2A82 + border-radius: 0 8px 8px 0 + color: #ffffff + font-size: 14px + cursor: pointer + transition: background-color 0.2s ease + display: flex + align-items: center + justify-content: center + box-sizing: border-box + line-height: normal + + &:hover + background-color: rgba(90, 42, 130, 0.9) + + html.dark & + background-color: #ffb91a + border-color: #ffb91a + color: #1D0631 + + &:hover + background-color: rgba(255, 185, 26, 0.9) + + i + font-size: 14px + +// Modern Search/Filter Bar (for projects index and other listing pages) +.search-filter-bar + background-color: #ffffff + border-radius: 12px + border: 1px solid rgba(229, 231, 235, 0.8) + padding: 16px + margin-bottom: 12px + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) + transition: background-color 0.3s ease + + html.dark & + background-color: #2D1548 + border-color: rgba(255, 255, 255, 0.1) + box-shadow: 0 1px 3px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.48) + + .search-filter-content + display: flex + align-items: center + gap: 12px + flex-wrap: wrap + + .commits_display_date_range + font-size: 13px + color: #6b7280 + white-space: nowrap + + html.dark & + color: #d1d5db + + .search-section + flex: 1 + display: flex + align-items: center + gap: 8px + min-width: 300px + + label + font-size: 14px + color: #6b7280 + white-space: nowrap + line-height: 40px + margin: 0 + padding: 0 + + html.dark & + color: #d1d5db + + .search-input-wrapper + flex: 1 + position: relative + + input + width: 100% + height: 40px + padding: 0 40px 0 12px + background-color: #ffffff + border: 1px solid #d1d5db + border-radius: 8px !important + color: #111827 + font-size: 14px + font-weight: 400 + line-height: normal + margin: 0 + box-sizing: border-box + transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease + + &::placeholder + color: #9ca3af + + &:hover + background-color: #f9fafb + border-color: #5A2A82 + + &:focus + outline: none + background-color: #ffffff + border-color: #5A2A82 + box-shadow: 0 0 0 3px rgba(90, 42, 130, 0.1) + + html.dark & + background-color: #1D0631 + border-color: #5A2A82 + color: #ffffff + + &::placeholder + color: #9ca3af + + &:hover + background-color: rgba(29, 6, 49, 0.8) + border-color: #ffb91a + + &:focus + background-color: #1D0631 + border-color: #ffb91a + box-shadow: 0 0 0 3px rgba(255, 185, 26, 0.1) + + .search-cancel-btn + position: absolute + right: 8px + top: 50% + transform: translateY(-50%) + background: transparent + border: none + color: #9ca3af + font-size: 14px + cursor: pointer + padding: 4px 6px + line-height: 1 + transition: color 0.15s ease + + &:hover + color: #6b7280 + + html.dark & + color: #6b7280 + + &:hover + color: #d1d5db + + .search-refresh-btn + display: flex + align-items: center + justify-content: center + height: 40px + width: 40px + padding: 0 + background-color: #5A2A82 + border: 1px solid #5A2A82 + border-radius: 8px + color: #ffffff + font-size: 14px + cursor: pointer + transition: background-color 0.2s ease, opacity 0.2s ease + box-sizing: border-box + flex-shrink: 0 + + &:hover + opacity: 0.9 + + html.dark & + background-color: #ffb91a + border-color: #ffb91a + color: #1D0631 + + &:hover + opacity: 0.9 + + i + font-size: 14px + + .sort-section + display: flex + align-items: center + gap: 8px + + label + font-size: 14px + color: #6b7280 + white-space: nowrap + + html.dark & + color: #d1d5db + + // Custom Sort Dropdown + .custom-sort-dropdown + position: relative + display: inline-block + + .sort-dropdown-btn + display: flex + align-items: center + gap: 8px + height: 40px + padding: 0 12px + background-color: #f9fafb + border: 1px solid #d1d5db + border-radius: 8px + color: #111827 + font-size: 14px + font-weight: 400 + min-width: 180px + cursor: pointer + transition: background 0.15s ease, border-color 0.15s ease + white-space: nowrap + box-sizing: border-box + + &:hover + background-color: #f3f4f6 + border-color: #5A2A82 + + html.dark & + background-color: #1D0631 + border-color: #5A2A82 + color: #ffffff + + &:hover + background-color: rgba(29, 6, 49, 0.8) + border-color: #ffb91a + + .selection + flex: 1 + text-align: left + + i + font-size: 12px + transition: transform 0.2s ease + + &.open + .sort-dropdown-menu + display: block + + .sort-dropdown-menu + display: none + position: absolute + top: 100% + padding: 0 + left: 0 + margin-top: 4px + min-width: 180px + background-color: #ffffff + border: 1px solid #d1d5db + border-radius: 8px + overflow: hidden + z-index: 1000 + // Material Design elevation shadow (level 3) + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23) + + html.dark & + background-color: #1D0631 + border-color: #5A2A82 + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.38), 0 6px 6px rgba(0, 0, 0, 0.46) + + .sort-dropdown-item + display: block + width: 100% + padding: 8px 16px + color: #111827 + font-size: 14px + font-weight: 400 + text-align: left + text-decoration: none + background: transparent + border: none + box-sizing: border-box + transition: background 0.15s ease, color 0.15s ease + cursor: pointer + + &:hover + background: #f3f4f6 + + &.active + background-color: #5A2A82 !important + color: #ffffff + font-weight: 400 + + &:hover + background-color: #5A2A82 + + html.dark & + color: #ffffff + + &:hover + background: #374151 + + &.active + background-color: #ffb91a !important + color: #5A2A82 + font-weight: 400 + + &:hover + background-color: #ffb91a + + .sort-value-input + display: none + + // Chosen select plugin support (for tag filtering) #value_select - display: inline !important + display: inline-block + margin-left: 8px + select - width: 400px - .col-md-5 - width: 43% - - .col-md-4 - width: 32% - #sort_by - float: right - padding-right: 10px - - label.paginate - font-size: 13px - button.btn - height: 34px - border-top: 10px - input - width: 180px - color: #000 - select - width: 150px + min-width: 300px + + // Checkbox filters label.checkbox + display: inline-flex + align-items: center + gap: 6px + font-size: 14px + color: #6b7280 + margin: 0 12px 0 0 + cursor: pointer + font-weight: normal + + html.dark & + color: #d1d5db + input - margin-top: 4px - label.radio - input - width: 13px !important - margin-top: 4px - #human_edits - input - width: 14px + width: 16px + height: 16px + margin: 0 + cursor: pointer + + #human_edits, #enlistment_edits input - width: 14px - -#nav-top-bar .new_main_menu form#quicksearch - margin-top: -4px - button.submit - background: none - border: 0 - ul.dropdown-menu - border-radius: 0px - margin-top: -10px !important - li - display: inline - a - @include search-dropdown-link-color - font-size: 14px - text-transform: none - &:hover - @include search-dropdown-link-hover-colors - .btn-small - font-size: 10px - border-width: 2px !important - margin-right: 0px !important - .selection - font-weight: 100 - text-transform: none - &:hover - @include search-button-hover-colors - -@media (min-width: 320px) and (max-width: 360px) - form#quicksearch - margin-top: -20px !important - margin-right: -61px !important - .btn-small - font-size: 8px !important -@media (min-width: 361px) and (max-width: 480px) - form#quicksearch - margin-top: -6px !important - margin-right: -61px !important - .btn-small - font-size: 8px !important - -.global_top_search - height: 18px !important - @include global-top-search-colors - -.global_top_search_icon - @include global-top-search-icon-color - padding-bottom: 5px - font-size: 18px - -@media (min-width: 320px) and (max-width: 480px) - .global_top_search - height: 15px !important - font-size: 8px !important - .global_top_search_icon - font-size: 10px !important + width: 16px + height: 16px + + // Radio button filters + label.radio + display: inline-flex + align-items: center + gap: 6px + font-size: 14px + color: #6b7280 + margin: 0 12px 0 0 + cursor: pointer + font-weight: normal + + html.dark & + color: #d1d5db + + input + width: 16px + height: 16px + margin: 0 + cursor: pointer + + &.inline + display: inline-flex + +// Pagination info shown between filter bar and results +.pagination-info + font-size: 15px + color: #6b7280 + margin-bottom: 12px + + html.dark & + color: #9ca3af + + .page-number + font-weight: 600 + +// Responsive adjustments for search-filter-bar +@media (max-width: 640px) + .search-filter-bar + .search-filter-content + .search-section, + .sort-section + width: 100% + min-width: 0 + + label.checkbox, + label.radio + width: 100% + + #value_select + width: 100% + margin-left: 0 + + select + min-width: 0 + width: 100% + + .commits_display_date_range + width: 100% + margin-left: 0 + font-size: 12px + order: 10 diff --git a/app/assets/stylesheets/session_projects.sass b/app/assets/stylesheets/session_projects.sass index b0f3d3e7f..8041df5cf 100644 --- a/app/assets/stylesheets/session_projects.sass +++ b/app/assets/stylesheets/session_projects.sass @@ -1,44 +1,250 @@ -.sp_label - margin-right: 10px - #sp_menu position: fixed - left: 50% - transform: translate(-50%, 0) - top: 0 - width: 960px - z-index: 1 - color: white - .inner - background-color: #0088cc - padding: 10px 20px 0 - box-shadow: 0 5px 0 #666 - -webkit-box-shadow: 0 5px 0 #666 - -ms-filter: "progid:DXImageTransform.Microsoft.DropShadow(color='#666666',offX='0', offY='5')" - ul.nav li - margin-right: 3em - &.last - margin-right: 0 - .sp_form - margin-bottom: 0 - .sp_label - color: #fff - margin-right: 0 - .sp_input - font-size: 17px - line-height: 17px - vertical-align: middle - margin-top: 2px + bottom: 0 + left: 0 + right: 0 + z-index: 1000 + display: none + transition: all 0.3s ease + + // Toggle Tab Button + .compare-toggle-tab + display: flex + justify-content: center + + .compare-toggle-button + display: flex + align-items: center + gap: 8px + padding: 8px 20px + background-color: #5A2A82 + color: #ffffff + border: none + border-radius: 12px 12px 0 0 + font-size: 14px + font-weight: 600 + cursor: pointer + box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.15) + transition: all 0.2s ease + + &:hover + opacity: 0.9 + + html.dark & + background-color: #ffb91a + color: #1D0631 + box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.3) + + .toggle-icon + transition: transform 0.2s ease + font-size: 12px + + &.rotate + transform: rotate(180deg) + + // Expandable Tray Content + .compare-tray-content + max-height: 0 + overflow: hidden + background-color: #ffffff + border-top: 1px solid #e5e7eb + box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.12) + transition: max-height 0.3s ease + + &.expanded + max-height: 280px + + html.dark & + background-color: #2D1548 + border-top-color: #5A2A82 + box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.4) + + .compare-tray-inner + max-width: 1200px + margin: 0 auto + padding: 16px 24px + + @media (max-width: 768px) + padding: 12px 16px + + // Projects Grid + .compare-projects-grid + display: grid + grid-template-columns: repeat(2, 1fr) + gap: 12px + margin-bottom: 16px + + @media (min-width: 640px) + grid-template-columns: repeat(3, 1fr) + + .compare-project-slot + position: relative + background-color: #f9fafb + border: 1px solid #e5e7eb + border-radius: 12px + padding: 12px + display: flex + align-items: start + gap: 12px + min-height: 80px + + html.dark & + background-color: #1D0631 + border-color: #5A2A82 + + &.empty-slot + border: 2px dashed #d1d5db + flex-direction: column + align-items: center + justify-content: center + color: #9ca3af + + html.dark & + border-color: #5A2A82 + color: #6b7280 + + i + font-size: 20px + margin-bottom: 4px + + span + font-size: 12px + + .remove-project + position: absolute + top: 8px + right: 8px + width: 20px + height: 20px + padding: 0 + background-color: #e5e7eb + border: none + border-radius: 50% + color: #6b7280 + cursor: pointer + display: flex + align-items: center + justify-content: center + transition: all 0.15s ease + + &:hover + background-color: #fee2e2 + color: #ef4444 + + html.dark & + background-color: rgba(255, 255, 255, 0.1) + color: #9ca3af + + &:hover + background-color: rgba(239, 68, 68, 0.2) + color: #fca5a5 + + i + font-size: 10px + + .project-slot-icon + flex-shrink: 0 + width: 40px + height: 40px + background-color: #e5e7eb + border: 1px solid #d1d5db + border-radius: 8px + display: flex + align-items: center + justify-content: center + overflow: hidden + + html.dark & + background-color: rgba(255, 255, 255, 0.1) + border-color: #6b7280 + + img + width: 100% + height: 100% + object-fit: cover + + i + font-size: 18px + color: #9ca3af + + .project-slot-info + flex: 1 + min-width: 0 + padding-right: 20px + + .project-slot-name + font-size: 14px + font-weight: 600 + color: #111827 + margin: 0 0 4px 0 + white-space: nowrap + overflow: hidden + text-overflow: ellipsis + + html.dark & + color: #ffffff + + .project-slot-meta + font-size: 12px + color: #6b7280 + margin: 2px 0 + + html.dark & + color: #9ca3af + + // Compare Action + .compare-action + display: flex + justify-content: center + + form + width: auto + + .btn-compare-submit + padding: 10px 32px + background-color: #5A2A82 + border: 1px solid #5A2A82 + border-radius: 8px + color: #ffffff + font-size: 14px + font-weight: 600 cursor: pointer - .compare - float: right - font-weight: bold - .limit - display: inline - margin-left: 0.5em - -#compare-submit-button - margin: -2px 0px 10px - padding: 0px 12px 1px - height: 29px - width: 75px + transition: all 0.2s ease + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23) + + &:hover:not(:disabled) + opacity: 0.9 + + &:disabled + background-color: #d1d5db + border-color: #d1d5db + color: #9ca3af + cursor: not-allowed + box-shadow: none + + html.dark & + background-color: #ffb91a + border-color: #ffb91a + color: #1D0631 + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.32), 0 3px 6px rgba(0, 0, 0, 0.46) + + &:disabled + background-color: #4b5563 + border-color: #4b5563 + color: #6b7280 + +// Add bottom padding when compare tray is visible +#projects_index_page.has-compare-tray + .projects-container + padding-bottom: 120px + +// Compare checkbox disabled state +.compare-checkbox.disabled + opacity: 0.5 + cursor: not-allowed !important + + &:hover + color: #9ca3af !important + + html.dark & + color: #6b7280 !important diff --git a/app/assets/stylesheets/sessions.sass b/app/assets/stylesheets/sessions.sass index a61e7092c..29ac4ec9e 100644 --- a/app/assets/stylesheets/sessions.sass +++ b/app/assets/stylesheets/sessions.sass @@ -1,34 +1,153 @@ -#sign-in-options - @include sign_buttons +// ── Signin page reuses .signup-page layout from accounts/new.sass ───────────── +// Only signin-specific overrides are here + +// ── Orb positions (signin mirrors signup: top-left + bottom-right) ───────────── +.signin-orb--top-left + top: -96px + left: -96px + width: 320px + height: 320px + background: rgba(90, 42, 130, 0.4) + filter: blur(48px) + +.signin-orb--bottom-right + bottom: -128px + right: -80px + width: 384px + height: 384px + background: rgba(255, 185, 26, 0.1) + filter: blur(48px) + +.signin-orb--center + top: 50% + left: 50% + transform: translate(-50%, -50%) + width: 500px + height: 500px + background: rgba(88, 28, 135, 0.2) + filter: blur(48px) + +// ── Badge pulsing dot ────────────────────────────────────────────────────────── +.signin-badge__dot--pulse + animation: pulse-dot 2s ease-in-out infinite + +@keyframes pulse-dot + 0%, 100% + opacity: 1 + 50% + opacity: 0.4 + +// ── Feature icon variants (signin-specific colors) ──────────────────────────── +.signin-feature--purple + background: linear-gradient(135deg, #8b5cf6, #4f46e5) !important + +.signin-feature--amber + background: linear-gradient(135deg, #ffb91a, #f97316) !important + +.signin-feature--green + background: linear-gradient(135deg, #10b981, #0d9488) !important + +.signin-feature--pink + background: linear-gradient(135deg, #ec4899, #e11d48) !important + +// ── Testimonial at bottom of left panel ─────────────────────────────────────── +.signin-testimonial + font-size: 11px + color: rgba(255, 255, 255, 0.5) + font-style: italic + line-height: 1.7 + margin: 0 0 8px + +.signin-testimonial-attr + font-size: 11px + color: rgba(255, 255, 255, 0.4) + margin: 0 + +// ── Remember me checkbox ────────────────────────────────────────────────────── +.signin-remember-me + margin-bottom: 16px + +.signin-checkbox-label + display: flex + align-items: center + gap: 8px + font-size: 13px + color: #374151 + cursor: pointer + margin: 0 + font-weight: 500 + + html.dark & + color: #d1d5db + + input[type="checkbox"] + width: 16px + height: 16px + border: 1px solid #d1d5db + border-radius: 4px + accent-color: #5A2A82 + cursor: pointer + flex-shrink: 0 + + html.dark & + accent-color: #ffb91a + border-color: rgba(90, 42, 130, 0.5) + +// ── Helper links (Forgot Password / Resend Activation) ──────────────────────── +.signin-helper-links + display: flex + flex-direction: column + align-items: center + gap: 8px + margin-top: 20px + margin-bottom: 20px + +.signin-helper-link + font-size: 13px + color: #6b7280 + text-decoration: none + transition: color 0.15s + + &:hover + color: #5A2A82 + text-decoration: underline + + html.dark & + color: #9ca3af + + html.dark &:hover + color: #ffb91a + +.signin-helper-link--primary + color: #5A2A82 + font-weight: 600 + + &:hover + color: #4a1f6e + + html.dark & + color: #ffb91a + + html.dark &:hover + color: #fbbf24 + +// ── Signin form inputs (same scope pattern as signup) ──────────────────────── +.signup-form-card + #signin_form + fieldset + border: none !important + padding: 0 !important + margin: 0 !important + + legend + display: none !important + +// Hide form fields until "Sign in with Email" is clicked — JS calls .show() #sign-in-fields - .github-oauth - padding: 2% 0 !important - font-size: 21px - margin-bottom: 2% display: none -@media only all and (min-width: 320px) and (max-width: 340px) - #sign-in-options - width: auto - #sign-in-fields - .github-oauth - padding: 2% 0 !important - font-size: 12px - margin-bottom: 2% - -@media only all and (min-width: 341px) and (max-width: 480px) - #sign-in-options - width: auto - #sign-in-fields - .github-oauth - font-size: 10px - -@media (min-width: 768px) and (max-width: 1024px) - .btn-block - width: auto !important - #sign-in-fields - .github-oauth - padding: 2% 0 !important - font-size: 13px - margin-bottom: 2% - +@media only all and (min-width: 320px) and (max-width: 480px) + .signin-orb--top-left, + .signin-orb--bottom-right, + .signin-orb--center + display: none diff --git a/app/assets/stylesheets/sidebar.sass b/app/assets/stylesheets/sidebar.sass index d8a2f583c..f6092085d 100644 --- a/app/assets/stylesheets/sidebar.sass +++ b/app/assets/stylesheets/sidebar.sass @@ -1,114 +1,367 @@ +// Design System Variables +$purple-dark: #1D0631 +$purple-primary: #5A2A82 +$purple-light: #9F7ABA +$yellow-accent: #FFB91A +$yellow-hover: #FFCC4D + +// Neutrals +$gray-50: #f9fafb +$gray-100: #f3f4f6 +$gray-200: #e5e7eb +$gray-300: #d1d5db +$gray-400: #9ca3af +$gray-500: #6b7280 +$gray-600: #4b5563 +$gray-700: #374151 +$gray-900: #111827 + +// Shadows +$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05) +$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) + .footer-navigation + background: white + border-radius: 24px + margin: 0 16px + padding: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + margin-bottom: 24px + + @media (min-width: 640px) + padding: 32px + margin: 0 32px + margin-bottom: 32px + a - @include site-link-color - .sidebar-nav - line-height: 12px - &:hover - line-height: 12px - a - border-radius: 0px - padding-left: 5px - padding-right: 10px - - .nav-active - @include bottom-nav-active-colors - margin-left: -3px - - .nav-hover - @include bottom-nav-hover-colors - border-radius: 0px - margin-left: -3px - - .navbar-justify - border-radius: 0px - - .actions - line-height: 0.5em - h4 - font-size: 15px - h4.linked - padding: 6px - margin-top: 2px - .footer-nav - &.nav-active - @extend .nav-active - margin-left: 15px - padding-left: 0px - .footer-nav - &:hover - @extend .nav-hover - margin-left: 15px - padding-left: 18px - border-top: -10px - .footer-navbar, h4 - padding-top: 2px - padding-bottom: 6px - margin-top: 8px - margin-bottom: 0px - a - @include site-link-color - &:hover + &:link, + &:visited, + &:hover, + &:active + text-decoration: none + + // Desktop: 4-column grid + .footer-nav-grid + display: none + + @media (min-width: 768px) + display: grid + grid-template-columns: repeat(4, 1fr) + gap: 32px + + .nav-column + .column-header + display: flex + align-items: center + gap: 8px + margin-bottom: 12px + padding-bottom: 8px + border-bottom: 1px solid $gray-200 + + a + display: flex + align-items: center + gap: 8px text-decoration: none - .icon - background-image: asset_url('icons/ohloh-icon-sprite.png') - height: 24px - width: 24px - float: left - margin-right: 6px - margin-top: -3px - &.project_summary,&.account_summary,&.org_summary - background-position: -448px -8px - &.contributions - background-position: -768px -8px - &.recognition - background-position: -807px -8px - &.usage - background-position: -848px -8px - &.code_data - background-position: -489px -6px - margin-top: -5px - margin-left: 1px - &.scm_data - background-position: -528px -6px - margin-top: -5px - &.user_data - background-position: -568px -6px - margin-top: -4px - &.selected - @extend .nav-active, .navbar-justify - margin-right: 20px - padding: 6px - &:hover - @extend .nav-hover, .navbar-justify - margin-right: 20px - &.linked - &:hover - @extend .nav-hover, .navbar-justify - margin-right: 20px -@media only all and (min-width: 320px) and (max-width: 480px) - .col-xs-offset-1 - margin-left: 0px !important - .footer-navigation - .actions - margin-bottom: 0px !important - margin-top: 0px !important - line-height: 10px - h4 - font-size: 10px - .footer-nav - padding-left: 0px -@media only all and (min-width: 540px) and (max-width: 1024px) + flex: 1 + + svg + width: 20px + height: 20px + color: $purple-primary + flex-shrink: 0 + + .icon + display: inline-block + background-image: asset_url('icons/ohloh-icon-sprite.png') + width: 24px + height: 24px + flex-shrink: 0 + background-repeat: no-repeat + filter: brightness(0) saturate(100%) invert(24%) sepia(35%) saturate(1697%) hue-rotate(245deg) brightness(90%) contrast(90%) + + &.project_summary, + &.account_summary, + &.org_summary + background-position: -448px -8px + + &.contributions + background-position: -768px -8px + + &.recognition + background-position: -807px -8px + + &.usage + background-position: -848px -8px + + &.code_data + background-position: -489px -6px + + &.scm_data + background-position: -528px -6px + + &.user_data + background-position: -568px -6px + + h4 + font-size: 14px + font-weight: 600 + color: $gray-900 + margin: 0 + white-space: nowrap + + &.selected + color: $purple-primary + font-weight: 700 + + ul + list-style: none + padding: 0 + margin: 0 + + li + margin-bottom: 8px + + &:last-child + margin-bottom: 0 + + &.nav-active + background-color: rgba(90, 42, 130, 0.08) + border-left: 3px solid $purple-primary + margin-left: -8px + padding-left: 5px + padding-top: 4px + padding-bottom: 4px + border-radius: 4px + + a + color: $purple-primary !important + font-weight: 600 + + a + font-size: 14px + color: #5A2A82 !important + text-decoration: none + transition: color 0.15s + + &:hover + text-decoration: underline + color: $purple-dark !important + + &:visited, + &:active + color: $purple-primary + + // Mobile: Accordion + .footer-nav-accordion + display: flex + flex-direction: column + + @media (min-width: 768px) + display: none + + .accordion-item + border-bottom: 1px solid $gray-200 + + &:last-child + border-bottom: none + + .accordion-header-btn + width: 100% + display: flex + align-items: center + justify-content: space-between + padding: 12px 0 + background: none + border: none + cursor: pointer + + .header-left + display: flex + align-items: center + gap: 10px + + svg + width: 20px + height: 20px + color: $purple-primary + flex-shrink: 0 + + .icon + display: inline-block + background-image: asset_url('icons/ohloh-icon-sprite.png') + width: 24px + height: 24px + flex-shrink: 0 + background-repeat: no-repeat + filter: brightness(0) saturate(100%) invert(24%) sepia(35%) saturate(1697%) hue-rotate(245deg) brightness(90%) contrast(90%) + + &.project_summary, + &.account_summary, + &.org_summary + background-position: -448px -8px + + &.contributions + background-position: -768px -8px + + &.recognition + background-position: -807px -8px + + &.usage + background-position: -848px -8px + + &.code_data + background-position: -489px -6px + + &.scm_data + background-position: -528px -6px + + &.user_data + background-position: -568px -6px + + span + font-size: 14px + font-weight: 600 + color: $gray-900 + + .chevron + width: 16px + height: 16px + color: $gray-600 + transition: transform 0.2s + + &.expanded + transform: rotate(180deg) + + .accordion-content-footer + padding-bottom: 12px + padding-left: 24px + display: none + + &.expanded + display: block + + ul + list-style: none + padding: 0 + margin: 0 + + li + margin-bottom: 6px + + &.nav-active + background-color: rgba(90, 42, 130, 0.08) + border-left: 3px solid $purple-primary + margin-left: -8px + padding-left: 5px + padding-top: 4px + padding-bottom: 4px + border-radius: 4px + + a + font-weight: 600 + + a + font-size: 14px + color: $purple-primary + text-decoration: none + + &:hover + text-decoration: underline + + &:visited, + &:active + color: $purple-primary + +// ============================================================================ +// Dark Mode +// ============================================================================ +html.dark, +body.dark, +.dark .footer-navigation - .actions - line-height: 12px -#page - .footer-nav - &:hover - @extend .nav-hover - margin-left: 10px - padding-left: 10px !important - font-size: 12px !important - &.nav-active - @extend .nav-active - margin-left: 0px !important - padding-left: 0px !important + background: #2D1548 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + .footer-nav-grid + .nav-column + .column-header + border-bottom-color: rgba(159, 122, 186, 0.3) + + svg + color: $yellow-accent + + .icon + filter: brightness(0) saturate(100%) invert(84%) sepia(39%) saturate(1638%) hue-rotate(347deg) brightness(103%) contrast(98%) + + h4 + color: #f8fafc + + ul + li + &.nav-active + background-color: rgba(255, 185, 26, 0.12) + border-left: 3px solid $yellow-accent + margin-left: -8px + padding-left: 5px + padding-top: 4px + padding-bottom: 4px + border-radius: 4px + + a + color: $yellow-accent !important + + &:visited, + &:active + color: $yellow-accent !important + + .footer-nav-accordion + .accordion-item + border-bottom-color: rgba(159, 122, 186, 0.3) + + .accordion-header-btn + .header-left + svg + color: $yellow-accent + + .icon + filter: brightness(0) saturate(100%) invert(84%) sepia(39%) saturate(1638%) hue-rotate(347deg) brightness(103%) contrast(98%) + + span + color: #f8fafc + + .chevron + color: #94a3b8 + + .accordion-content-footer + ul + li + &.nav-active + background-color: rgba(255, 185, 26, 0.12) + border-left: 3px solid $yellow-accent + margin-left: -8px + padding-left: 5px + padding-top: 4px + padding-bottom: 4px + border-radius: 4px + + a + font-weight: 600 + + a + color: $yellow-accent !important + + &:visited, + &:active + color: $yellow-accent !important + +// ============================================================================ +// Backward Compatibility Stub +// ============================================================================ +// Keeping .actions for clearance.sass @extend compatibility +// The footer navigation no longer uses .actions, but clearance.sass +// has .submit-field that extends it +.actions + line-height: 0.5em + h4 + font-size: 15px diff --git a/app/assets/stylesheets/stacks.sass b/app/assets/stylesheets/stacks.sass index 6039daee1..6ca4056b8 100644 --- a/app/assets/stylesheets/stacks.sass +++ b/app/assets/stylesheets/stacks.sass @@ -1,17 +1,31 @@ +h2.pull-left + font-weight: 700 !important + font-size: 24px !important + a.promo display: block border: medium none - background-color: #F2F2F2 h6 color: #369 .widget_preview margin: 0px auto - width: 110px .stack border: 1px solid #BBB - width: 80px padding: 8px background-color: #FFF + overflow: hidden + .icons + display: flex + flex-wrap: wrap + gap: 4px + .icon-container + width: 20px + height: 20px + border-radius: 3px + overflow: hidden + box-shadow: none + .icon-letter + font-size: 12px .ohloh margin: 0.8em 0px 0px text-align: center @@ -25,6 +39,52 @@ a.promo .inset padding: 0.5em 6px 0px +.recommendations + a#show + text-decoration: none + &:hover + text-decoration: none + +#add_project_form + margin-bottom: 20px + .form-control + border: 2px solid #e5e7eb !important + border-radius: 16px !important + -webkit-border-radius: 16px !important + -moz-border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + &::placeholder + color: #9ca3af !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #d1d5db !important + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(14, 75, 122, 0.1) !important + background: #ffffff !important + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &::placeholder + color: #6b7280 !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.1) !important + border-color: #6b7280 !important + &:focus, &:focus-visible + border-color: #ffb91a !important + background: #1D0631 !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + outline: none !important + #stacks_show_page .promo-more-link float: right diff --git a/app/assets/stylesheets/static_forms.sass b/app/assets/stylesheets/static_forms.sass index 3c5734cd8..fb31374f5 100644 --- a/app/assets/stylesheets/static_forms.sass +++ b/app/assets/stylesheets/static_forms.sass @@ -697,3 +697,31 @@ span.add-on -moz-box-sizing: content-box -ms-box-sizing: content-box box-sizing: content-box + +// Focus styles — remove Bootstrap's blue ring for mouse users, keep visible indicator for keyboard users +.form-control:focus + border-color: #d1d5db !important + box-shadow: none !important + +.form-control:focus-visible + border-color: #5A2A82 !important + outline: 2px solid #5A2A82 !important + outline-offset: 2px + box-shadow: none !important + +.form-control:focus:not(:focus-visible) + outline: none !important + +a:focus-visible + outline: 2px solid #5A2A82 !important + outline-offset: 2px + +a:focus:not(:focus-visible) + outline: none !important + +select:focus-visible, input[type="file"]:focus-visible, input[type="radio"]:focus-visible, input[type="checkbox"]:focus-visible + outline: 2px solid #5A2A82 !important + outline-offset: 2px + +select:focus:not(:focus-visible), input[type="file"]:focus:not(:focus-visible), input[type="radio"]:focus:not(:focus-visible), input[type="checkbox"]:focus:not(:focus-visible) + outline: none !important diff --git a/app/assets/stylesheets/streamgraph.sass b/app/assets/stylesheets/streamgraph.sass index d63de56a2..cbb7c0006 100644 --- a/app/assets/stylesheets/streamgraph.sass +++ b/app/assets/stylesheets/streamgraph.sass @@ -3,11 +3,11 @@ height: 300px !important #ohloh_stream width: 940px !important - width: 715px - height: 350px !important - float: left - margin-left: 15px - margin-right: 10px + width: 100% + height: 300px !important + float: none + margin-left: 0 + margin-right: 0 .highlight stroke: #663300 stroke-width: 2px @@ -28,52 +28,69 @@ font-weight: bold #streamgraph_legend - width: 150px - overflow: auto - white-space: nowrap - float: right - margin-right: 15px - border: solid #c3c3c3 thin - padding-top: 5px + display: flex + flex-wrap: wrap + flex-direction: row + align-items: center + gap: 6px 16px + padding: 10px 4px 4px + border: none + width: auto + float: none + margin: 0 + overflow: visible + white-space: normal -#streamgraph_legend - div - dislplay: inline-block - p - @include site-link-color - padding-left: 10px - margin: 0 0 -2px 0 - dislplay: inline +.streamgraph_legend_item + display: flex + align-items: center + gap: 5px + font-size: 12px + cursor: default .streamgraph_legend_color - width: 16px + display: inline-block + width: 12px height: 12px border: solid #cccccc thin - float: left - margin: 4px 4px 0 4px border-radius: 3px - clear: left + flex-shrink: 0 -.full - .background-watermark - background: asset_url('charts/watermark_860.png') no-repeat center +.streamgraph_legend_label + @include site-link-color + font-size: 12px + line-height: 1 -.regular - .background-watermark - background: asset_url('charts/watermark_692.png') no-repeat center left @media only all and (min-width: 320px) and (max-width: 480px) #ohloh_streamgraph &.languages height: 150px !important + width: 100% !important + overflow-x: auto !important + -webkit-overflow-scrolling: touch #ohloh_stream - width: auto !important - width: auto !important + width: 100% !important + max-width: 100% !important + svg#ohloh_stream + width: 100% !important + max-width: 100% !important + height: 150px !important #streamgraph_legend - font-size: 13px !important + font-size: 11px !important + gap: 4px 10px !important +@media (min-width: 481px) and (max-width: 767px) + #ohloh_streamgraph + width: 100% !important + overflow-x: auto !important + -webkit-overflow-scrolling: touch + #ohloh_stream + width: 100% !important + max-width: 100% !important + svg#ohloh_stream + width: 100% !important + max-width: 100% !important @media (min-width: 540px) and (max-width: 1024px) #ohloh_streamgraph #ohloh_stream width: auto !important - width: auto !important - #streamgraph_legend - margin-right: 48px \ No newline at end of file + width: auto !important \ No newline at end of file diff --git a/app/assets/stylesheets/tags.sass b/app/assets/stylesheets/tags.sass index d69dafa2f..83f6ae2a3 100644 --- a/app/assets/stylesheets/tags.sass +++ b/app/assets/stylesheets/tags.sass @@ -9,3 +9,419 @@ margin: 0 2px 4px 0 padding: 5px 8px text-decoration: none + +// Tag Index page (browse by tag) +#tag-index-page + width: 95% + + .tag-index-header + display: flex + align-items: center + flex-wrap: wrap + gap: 12px + margin-bottom: 20px + padding-bottom: 16px + border-bottom: 1px solid #e5e7eb + html.dark & + border-bottom-color: rgba(255, 255, 255, 0.1) + + h1 + margin: 0 + font-size: 22px + font-weight: 600 + color: #111827 + html.dark & + color: #ffffff + + .tag-index-breadcrumb + color: #5A2A82 + text-decoration: none + &:hover + text-decoration: underline + html.dark & + color: #ffb91a + + .tag-index-breadcrumb-sep + color: #9ca3af + margin: 0 6px + font-weight: 300 + + .tag-index-pills + display: flex + flex-wrap: wrap + gap: 8px + margin-left: auto + + .tag-index-pill + display: inline-block + padding: 4px 12px + background: #ede9f7 + border: 1px solid #c4b5e8 + border-radius: 20px + font-size: 12px + font-weight: 600 + color: #5A2A82 + html.dark & + background: #2D1548 + border-color: #5A2A82 + color: #ffb91a + + .oh-card.tag-index-body + padding: 24px + + .tag-index-hint + color: #6b7280 + font-size: 14px + margin-bottom: 16px + html.dark & + color: #9ca3af + + .tag-index-empty + text-align: center + padding: 32px 16px + + p + color: #6b7280 + margin-bottom: 16px + html.dark & + color: #9ca3af + + .btn.btn-default + border-radius: 20px + padding: 8px 20px + font-weight: 500 + + @media (max-width: 767px) + width: 100% + .tag-index-header + flex-direction: column + align-items: flex-start + .tag-index-pills + margin-left: 0 + +// Tag Cloud page +#tag-cloud-page + width: 90% + + .oh-card + padding: 24px + + .tag-cloud-help + p + margin-bottom: 8px + color: #4b5563 + font-size: 14px + html.dark & + color: #9ca3af + + .form-control + box-sizing: border-box !important + border: 2px solid #e5e7eb !important + border-radius: 16px !important + -webkit-border-radius: 16px !important + -moz-border-radius: 16px !important + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important + background: #ffffff !important + color: #111827 !important + padding: 10px 16px !important + height: auto !important + width: 100% !important + transition: border-color 0.3s ease, box-shadow 0.3s ease + &::placeholder + color: #9ca3af !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important + border-color: #d1d5db !important + &:focus, &:focus-visible + border-color: #5A2A82 !important + outline: none !important + box-shadow: 0 0 0 4px rgba(90, 42, 130, 0.1) !important + background: #ffffff !important + html.dark & + background: #1D0631 !important + color: #ffffff !important + border-color: #5A2A82 !important + &::placeholder + color: #6b7280 !important + opacity: 1 + &:hover + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2) !important + border-color: #6b7280 !important + &:focus, &:focus-visible + border-color: #ffb91a !important + background: #1D0631 !important + box-shadow: 0 0 0 4px rgba(255, 185, 26, 0.1) !important + outline: none !important + +.tag-cloud-grid + display: flex + flex-wrap: wrap + gap: 10px + padding: 4px 0 + +.tag-cloud-item + display: inline-flex + align-items: center + gap: 5px + padding: 6px 14px + background: #f3f4f6 + border: 1px solid #e5e7eb + border-radius: 20px + font-size: 13px + font-weight: 500 + color: #5A2A82 + text-decoration: none + transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease + cursor: pointer + &:hover + background: #5A2A82 + color: #ffffff + border-color: #5A2A82 + text-decoration: none + box-shadow: 0 2px 8px rgba(90, 42, 130, 0.25) + html.dark & + background: #2D1548 + border-color: #5A2A82 + color: #ffb91a + &:hover + background: #ffb91a + color: #1D0631 + border-color: #ffb91a + box-shadow: 0 2px 8px rgba(255, 185, 26, 0.25) + + .tag-cloud-count + font-size: 11px + font-weight: 400 + opacity: 0.7 + + @media (max-width: 767px) + font-size: 12px + padding: 5px 10px + + +// ────────────────────────────────────────────────────────────────────────────── +// Project Tags Page (#project-tags-page) +// ────────────────────────────────────────────────────────────────────────────── + +#project-tags-page + padding: 24px 0 + + // ── Breadcrumb header ── + + .project-tags-header + margin-bottom: 24px + + .project-tags-title + font-size: 22px + font-weight: 700 + color: #111827 + margin: 0 0 8px 0 + display: flex + align-items: center + gap: 8px + flex-wrap: wrap + + html.dark & + color: #ffffff + + a + color: #5A2A82 + text-decoration: none + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + &__sep + color: #9ca3af + font-weight: 400 + + // ── Section titles ── + + .project-tags-card-title, + .project-tags-section-title + font-size: 16px + font-weight: 600 + color: #5A2A82 + margin: 0 0 16px 0 + + html.dark & + color: #ffb91a + + // ── Tags remaining count ── + + .project-tags-left + font-size: 13px + color: #6b7280 + margin-bottom: 12px + + html.dark & + color: #9ca3af + + // ── Current tags pill list ── + + .project-tags-current + display: flex + flex-wrap: wrap + gap: 6px + margin-bottom: 20px + min-height: 32px + + .project-tag + display: inline-flex + align-items: center + gap: 5px + padding: 5px 10px + background: #ede9f7 + border: 1px solid #c4b5e8 + border-radius: 20px + font-size: 12px + font-weight: 500 + color: #5A2A82 + text-decoration: none + cursor: pointer + transition: background 0.15s, border-color 0.15s + + html.dark & + background: #2D1548 + border-color: #5A2A82 + color: #ffb91a + + &:hover + background: #ddd6f3 + border-color: #a78bcc + + html.dark & + background: #3d1f5c + border-color: #7c4dba + + &__remove + font-size: 10px + opacity: 0.6 + margin-left: 2px + + &:hover + opacity: 1 + + // ── Add tag form ── + + .project-tags-form + &__label + display: block + font-size: 13px + font-weight: 600 + color: #374151 + margin-bottom: 8px + + html.dark & + color: #d1d5db + + &__row + display: flex + gap: 8px + align-items: flex-start + flex-wrap: wrap + + .form-control + flex: 1 + min-width: 160px + box-sizing: border-box + border: 2px solid #e5e7eb + border-radius: 16px + padding: 10px 16px + font-size: 14px + height: auto + background: #ffffff + color: #111827 + transition: border-color 0.2s, box-shadow 0.2s + + &::placeholder + color: #9ca3af + + &:focus + border-color: #5A2A82 + outline: none + box-shadow: 0 0 0 3px rgba(90, 42, 130, 0.1) + + html.dark & + background: #1D0631 + color: #ffffff + border-color: #5A2A82 + + &::placeholder + color: #6b7280 + + &:focus + border-color: #ffb91a + box-shadow: 0 0 0 3px rgba(255, 185, 26, 0.1) + + &__submit + height: 42px + padding: 0 20px + border-radius: 20px + font-size: 14px + font-weight: 600 + white-space: nowrap + + // ── Related project cards ── + + .related-project-item + padding: 14px 16px + margin-bottom: 10px + + &__header + display: flex + align-items: center + gap: 8px + margin-bottom: 10px + + &__name + display: flex + align-items: center + gap: 6px + font-size: 14px + font-weight: 600 + + a + color: #5A2A82 + text-decoration: none + + html.dark & + color: #ffb91a + + &:hover + text-decoration: underline + + &__tags + display: flex + flex-wrap: wrap + gap: 4px + + // ── Responsive ── + + @media (max-width: 767px) + padding: 16px 0 + + .project-tags-title + font-size: 17px + + .col-md-6 + margin-bottom: 16px + + .project-tags-section-title + margin-top: 8px + + .project-tags-form__row + flex-direction: column + + .form-control + width: 100% + + .project-tags-form__submit + width: 100% + + @media (min-width: 768px) and (max-width: 1024px) + padding: 20px 0 diff --git a/app/assets/stylesheets/tools.sass b/app/assets/stylesheets/tools.sass index 5dc7a88b6..17994e71c 100644 --- a/app/assets/stylesheets/tools.sass +++ b/app/assets/stylesheets/tools.sass @@ -1,2 +1,660 @@ -.language-list-lineheight - line-height: 55px +// Colors +$purple-primary: #5A2A82 +$purple-dark: #1D0631 +$purple-card-dark: #2D1548 +$yellow-accent: #ffb91a +$yellow-dark: #b8860b +$orange-accent: #FF6B35 +$blue-primary: #0E4B7A +$blue-light: #3b82f6 +$cyan: #61dafb +$green: #41b883 +$green-light: #22c55e + +// Grays +$gray-50: #f9fafb +$gray-100: #f3f4f6 +$gray-200: #e5e7eb +$gray-300: #d1d5db +$gray-400: #9ca3af +$gray-500: #6b7280 +$gray-600: #4b5563 +$gray-700: #374151 +$gray-900: #111827 + +// Tools Page Container - All selectors scoped to .tools-page to prevent CSS collisions +.tools-page + min-height: 100vh + background-color: white + + html.dark & + background-color: $purple-dark + + // Header + .tools-header + padding: 0 16px 10px + background-color: white + + html.dark & + background-color: $purple-dark + + .container + max-width: 1280px + margin: 0 auto + + .tools-title + font-size: 36px + font-weight: bold + color: $purple-primary + + html.dark & + color: white + + @media (max-width: 640px) + font-size: 30px + + // Main Content + .tools-content + padding: 0 0 48px + + .container + max-width: 1280px + margin: 0 auto + + // Tools Grid (3 columns) + .tools-grid + display: grid + grid-template-columns: 1fr + gap: 24px + margin-bottom: 48px + + @media (min-width: 768px) + grid-template-columns: repeat(3, 1fr) + + // Tool Card + .tool-card + background-color: white + border-radius: 12px + border: 1px solid $gray-200 + overflow: hidden + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) + transition: all 0.3s ease + + html.dark & + background-color: $purple-card-dark + border-color: rgba(255, 255, 255, 0.1) + + &:hover + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) + transform: scale(1.02) + + .tool-card-header + padding: 16px + border-bottom: 1px solid $gray-200 + display: flex + align-items: center + gap: 12px + + html.dark & + border-bottom-color: rgba(255, 255, 255, 0.1) + + .tool-icon + width: 40px + height: 40px + border-radius: 8px + display: flex + align-items: center + justify-content: center + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) + + i + font-size: 20px + color: white + + .tool-icon-orange + background: linear-gradient(to bottom right, #fbbf24, #f97316) + + .tool-icon-blue + background: linear-gradient(to bottom right, #3b82f6, #4f46e5) + + .tool-icon-green + background: linear-gradient(to bottom right, #22c55e, #14b8a6) + + .tool-card-title + margin: 0 + font-size: 18px + font-weight: 600 + + a + color: $gray-900 + text-decoration: none + transition: color 0.2s + + html.dark & + color: white + + &:hover + color: $purple-primary + + html.dark & + color: $yellow-accent + + .tool-card-body + padding: 24px + + .tool-description + font-size: 14px + color: $gray-600 + margin-bottom: 16px + line-height: 1.5 + + html.dark & + color: $gray-400 + + // Stats Grid - now scoped to .tools-page + .stats-grid + display: grid + grid-template-columns: repeat(2, 1fr) + gap: 12px + margin-bottom: 16px + + .stat-item + display: flex + align-items: center + gap: 8px + font-size: 14px + + span + color: $gray-600 + + html.dark & + color: $gray-400 + + .stat-dot + width: 12px + height: 12px + border-radius: 50% + + .stat-dot-cyan + background-color: $cyan + + .stat-dot-green + background-color: $green + + // Language Indicators + .language-indicators + margin-bottom: 16px + + .language-indicator + display: flex + align-items: center + gap: 8px + margin-bottom: 12px + + span + color: $gray-600 + + html.dark & + color: $gray-400 + + .lang-dot + width: 12px + height: 12px + border-radius: 50% + + .lang-dot-blue + background-color: #3b82f6 + + .lang-dot-green + background-color: #22c55e + + // Stat Badges + .stat-badges + margin-bottom: 16px + + .stat-badge + display: flex + align-items: center + gap: 8px + color: $gray-500 + margin-bottom: 8px + font-size: 14px + + html.dark & + color: $gray-400 + + i + width: 12px + height: 12px + + // Stat Times + .stat-times + margin-bottom: 16px + + .stat-time + display: flex + align-items: center + gap: 8px + color: $gray-600 + margin-bottom: 8px + font-size: 14px + + html.dark & + color: $gray-400 + + i + width: 12px + height: 12px + color: $gray-400 + + // Chart Image + .chart-image + display: block + width: 100% + height: auto + margin-top: 24px + border-radius: 8px + object-fit: contain + + html.dark & + filter: brightness(0.9) contrast(1.1) + + // Chart Legend + .chart-legend + display: grid + grid-template-columns: repeat(2, 1fr) + gap: 8px + margin-top: 16px + + .legend-item + display: flex + align-items: center + gap: 8px + font-size: 14px + + span + color: $gray-600 + + html.dark & + color: $gray-400 + + .legend-color + width: 12px + height: 12px + border-radius: 50% + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) + + // Bottom Grid (2 columns) + .tools-bottom-grid + display: grid + grid-template-columns: 1fr + gap: 24px + + @media (min-width: 1024px) + grid-template-columns: 2fr 1fr + + // Section Cards + .language-stats-section, + .additional-info-section + background-color: white + border-radius: 12px + border: 1px solid $gray-200 + overflow: hidden + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) + + html.dark & + background-color: $purple-card-dark + border-color: rgba(255, 255, 255, 0.1) + + .additional-info-section + align-self: start + + // Section Headers + .section-header + padding: 14px 24px + border-bottom: 1px solid $gray-200 + display: flex + align-items: center + justify-content: space-between + + html.dark & + border-bottom-color: rgba(255, 255, 255, 0.1) + + .section-header-simple + padding: 24px + border-bottom: 1px solid $gray-200 + + html.dark & + border-bottom-color: rgba(255, 255, 255, 0.1) + + .section-title + font-size: 24px + font-weight: 600 + color: $gray-900 + margin-bottom: 8px + margin-top: 8px !important + + html.dark & + color: white + + a + color: inherit + text-decoration: none + transition: color 0.2s + + &:hover + color: $purple-primary + + html.dark & + color: $yellow-accent + + .section-subtitle + font-size: 14px + color: $gray-600 + + html.dark & + color: $gray-400 + + .section-icon + width: auto + height: 100% + color: $purple-primary + opacity: 0.5 + font-size: 32px + display: flex + align-items: center + justify-content: center + + html.dark & + color: $yellow-accent + + // Section Content + .section-content + padding: 24px + + // Info Box + .info-box + background: linear-gradient(to bottom right, $gray-50, white) + border-radius: 8px + padding: 24px + margin-bottom: 24px + border: 1px solid $gray-200 + + html.dark & + background: linear-gradient(to bottom right, $purple-dark, $purple-card-dark) + border-color: rgba(255, 255, 255, 0.05) + + .info-text + font-size: 14px + color: $gray-700 + margin-bottom: 12px + line-height: 1.5 + + html.dark & + color: $gray-300 + + .info-badge + display: flex + align-items: center + gap: 8px + font-size: 12px + color: $gray-500 + background-color: white + border-radius: 6px + padding: 8px 12px + border: 1px solid $gray-200 + + html.dark & + background-color: $purple-dark + color: $gray-400 + border-color: rgba(255, 255, 255, 0.1) + + i + width: 14px + height: 14px + color: $purple-primary + + html.dark & + color: $yellow-accent + + // Language Cloud + .language-cloud-wrapper + position: relative + + &::before + content: '' + position: absolute + inset: 0 + background: linear-gradient(to right, rgba($purple-primary, 0.05), transparent, rgba($blue-primary, 0.05)) + border-radius: 8px + pointer-events: none + + html.dark & + background: linear-gradient(to right, rgba($purple-primary, 0.1), transparent, rgba($blue-primary, 0.1)) + + .language-cloud + position: relative + background-color: white + border-radius: 8px + padding: 24px + border: 1px solid $gray-200 + backdrop-filter: blur(4px) + + html.dark & + background-color: rgba($purple-dark, 0.5) + border-color: rgba(255, 255, 255, 0.1) + + display: flex + flex-wrap: wrap + gap: 12px 8px + justify-content: space-between + align-items: center + line-height: 55px + margin-bottom: 24px + + .language-tag + position: relative + text-decoration: none + transition: all 0.2s ease + color: $purple-primary + + html.dark & + color: $yellow-accent + + &:hover + color: $orange-accent + transform: scale(1.1) + + html.dark & + color: $yellow-accent + + // Language CTA + .language-cta + display: flex + align-items: center + justify-content: space-between + background: linear-gradient(to right, rgba($purple-primary, 0.05), rgba($blue-primary, 0.05)) + border-radius: 8px + padding: 16px + border: 1px solid $gray-200 + + html.dark & + background: linear-gradient(to right, rgba($purple-primary, 0.1), rgba($blue-primary, 0.1)) + border-color: rgba(255, 255, 255, 0.1) + + .cta-text + display: flex + align-items: center + gap: 8px + font-size: 14px + color: $gray-700 + + html.dark & + color: $gray-300 + + i + width: 16px + height: 16px + color: $purple-primary + + html.dark & + color: $yellow-accent + + .cta-button + display: inline-flex + align-items: center + gap: 8px + padding: 8px 16px + background-color: $purple-primary + color: white + border-radius: 8px + text-decoration: none + font-weight: 600 + font-size: 14px + transition: all 0.2s ease + + html.dark & + background-color: $yellow-accent + color: #1D0631 !important + + &:hover + background-color: rgba($purple-primary, 0.9) + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) + color: #ffffff + + html.dark & + background-color: rgba($yellow-accent, 0.9) + color: #1D0631 !important + + i + transform: translateX(4px) + + i + transition: transform 0.2s ease + + // Info Cards + .info-card + border-radius: 8px + padding: 20px + border: 1px solid $gray-200 + margin-bottom: 24px + + html.dark & + border-color: rgba(255, 255, 255, 0.1) + + .info-card-api + background: linear-gradient(to bottom right, #eff6ff, white) + + html.dark & + background: linear-gradient(to bottom right, $purple-dark, $purple-card-dark) + + .info-card-projects + background: linear-gradient(to bottom right, #f0fdf4, white) + + html.dark & + background: linear-gradient(to bottom right, $purple-dark, $purple-card-dark) + + .info-card-header + display: flex + align-items: center + gap: 8px + margin-bottom: 16px + + .info-card-icon + width: 32px + height: 32px + border-radius: 8px + display: flex + align-items: center + justify-content: center + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) + + i + width: 16px + height: 16px + color: white + + .info-icon-blue + background: linear-gradient(to bottom right, #3b82f6, #4f46e5) + + .info-icon-green + background: linear-gradient(to bottom right, #22c55e, #14b8a6) + + .info-card-title + font-size: 16px + font-weight: 600 + color: $gray-900 + margin: 0 + + html.dark & + color: white + + // Info List + .info-list + list-style: none + padding: 0 + margin: 0 + + li + display: flex + align-items: flex-start + gap: 8px + margin-bottom: 12px + color: $gray-600 + + html.dark & + color: $gray-400 + + &:last-child + margin-bottom: 0 + + .list-bullet + color: $purple-primary + margin-top: 4px + + html.dark & + color: $yellow-accent + + .list-content + display: flex + flex-direction: column + gap: 4px + + span + font-size: 14px + + .list-link + color: #2563eb + text-decoration: none + font-weight: 500 + transition: color 0.2s + + html.dark & + color: #60a5fa + + &:hover + color: $purple-primary + text-decoration: underline + + html.dark & + color: $yellow-accent + + // Responsive adjustments + @media (max-width: 768px) + .tools-header + padding: 16px 0 + + .tools-grid + gap: 16px + + .section-title + font-size: 20px + + .language-cta + flex-direction: column + gap: 16px + text-align: center + + .cta-button + width: 100% + justify-content: center diff --git a/app/assets/stylesheets/top_contributors_table.sass b/app/assets/stylesheets/top_contributors_table.sass new file mode 100644 index 000000000..cf4999e3f --- /dev/null +++ b/app/assets/stylesheets/top_contributors_table.sass @@ -0,0 +1,130 @@ +// Top Contributors Table Styling (Figma Design) +// This applies to all pages using _contributions.html.haml partial +// Targets tables with specific column structure (col-md-3 for Name column) + +// Card border styling for tables +table.table + background: #ffffff + border-radius: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + overflow: hidden + transition: box-shadow 0.2s + padding: 0 24px + + @media (min-width: 768px) + padding: 0 32px + + html.dark & + background: #2D1548 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + +// Only apply to tables that have the contributions structure +table.table + // Target only if table has thead with col-md-3 (Name column from contributions partial) + thead + + th.col-md-3 + text-align: center + vertical-align: middle + font-size: 11px + font-weight: 600 + color: #6b7280 + text-transform: uppercase + letter-spacing: 0.05em + border-bottom: none !important + + html.dark & + color: #9ca3af + +// Apply full styling to tables with contributions columns +table.table thead:has(th.col-md-3) + ~ tbody + tr + border-bottom: 1px solid #f3f4f6 + transition: background-color 0.2s + + html.dark & + border-bottom: 1px solid rgba(90, 42, 130, 0.2) + + &:hover + background-color: #f9fafb + + html.dark & + background-color: rgba(255, 255, 255, 0.05) + + td + text-align: center + padding: 12px + vertical-align: middle + border-bottom: none !important + + html.dark & + color: #e2e8f0 + + tr + border-bottom: 2px solid #e5e7eb + + html.dark & + border-bottom: 2px solid rgba(90, 42, 130, 0.4) + +// Fallback for browsers that don't support :has() +// Target tables in known contributors containers +.top-committers, +.col-md-12 + table.table + width: 100% + background: #ffffff !important + border-radius: 16px + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) + overflow: hidden + padding: 0 24px + + @media (min-width: 768px) + padding: 0 32px + + html.dark & + background: #2D1548 !important + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.48) + + thead + tr + border-bottom: 2px solid #e5e7eb + + html.dark & + border-bottom: 2px solid rgba(90, 42, 130, 0.4) + + th + text-align: center + vertical-align: middle + font-size: 11px + font-weight: 600 + color: #6b7280 + text-transform: uppercase + letter-spacing: 0.05em + border-bottom: none !important + + html.dark & + color: #9ca3af + + tbody + tr + border-bottom: 1px solid #f3f4f6 + transition: background-color 0.2s + + html.dark & + border-bottom: 1px solid rgba(90, 42, 130, 0.2) + + &:hover + background-color: #f9fafb + + html.dark & + background-color: rgba(255, 255, 255, 0.05) + + td + text-align: center + padding: 12px + vertical-align: middle + border-bottom: none !important + + html.dark & + color: #e2e8f0 diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 727e0f09f..e11d9b967 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -8,13 +8,16 @@ class AccountsController < ApplicationController helper MapHelper skip_before_action :store_location, only: %i[new create] - before_action :session_required, only: %i[edit destroy confirm_delete me] - before_action :set_account, only: %i[destroy show update edit confirm_delete disabled settings] + before_action :session_required, + only: %i[edit destroy confirm_delete me theme_preference set_theme_preference] + before_action :set_account, + only: %i[destroy show update edit confirm_delete disabled settings theme_preference + set_theme_preference] before_action :redirect_if_disabled, only: %i[show update edit] before_action :redirect_unverified_account, only: %i[edit update me] before_action :disabled_during_read_only_mode, only: %i[edit update] before_action :account_context, only: %i[edit update confirm_delete] - before_action :must_own_account, only: %i[edit update destroy confirm_delete] + before_action :must_own_account, only: %i[edit update destroy confirm_delete theme_preference set_theme_preference] before_action :find_claimed_people, only: :index before_action :redirect_if_logged_in, only: :new before_action :check_honeypot, only: :create @@ -91,6 +94,19 @@ def disabled; end def settings; end + def theme_preference + respond_to do |format| + format.json { render json: { theme_preference: @account.theme_preference } } + end + end + + def set_theme_preference + @account.theme_preference = params[:theme] + render json: { success: true, theme: @account.theme_preference } + rescue ArgumentError + render json: { error: 'Invalid theme preference' }, status: :unprocessable_entity + end + private def find_claimed_people diff --git a/app/controllers/explore_controller.rb b/app/controllers/explore_controller.rb index 687b3f2f5..c3d75c112 100644 --- a/app/controllers/explore_controller.rb +++ b/app/controllers/explore_controller.rb @@ -23,7 +23,7 @@ def orgs end def orgs_by_thirty_day_commit_volume - @org_by_30_day_commits = OrgThirtyDayActivity.filter(params[:filter]) + @org_by_30_day_commits = OrgThirtyDayActivity.filter(params[:org_type]) end def projects; end diff --git a/app/controllers/feedbacks_controller.rb b/app/controllers/feedbacks_controller.rb index 3ea83b1e9..4d5d835c4 100644 --- a/app/controllers/feedbacks_controller.rb +++ b/app/controllers/feedbacks_controller.rb @@ -7,6 +7,7 @@ class FeedbacksController < ApplicationController def set_access_control_header headers['Access-Control-Allow-Origin'] = 'https://security.openhub.net' + headers['Access-Control-Allow-Private-Network'] = 'true' if request.method == 'OPTIONS' end def create diff --git a/app/controllers/project_tags_controller.rb b/app/controllers/project_tags_controller.rb index f968ab9f0..c777babe9 100644 --- a/app/controllers/project_tags_controller.rb +++ b/app/controllers/project_tags_controller.rb @@ -14,7 +14,10 @@ class ProjectTagsController < SettingsController def index; end def create - @project.update!(tag_list: "#{@project.tag_list} #{params[:tag_name]}") + tag_name = params[:tag_name].to_s.strip + return render plain: custom_description_error, status: :unprocessable_entity if duplicate_tag?(tag_name) + + @project.update!(tag_list: "#{@project.tag_list} #{tag_name}") render plain: ERB::Util.html_escape(@project.tag_list).split.sort.join("\n") rescue StandardError render_create_error @@ -37,6 +40,10 @@ def status private + def duplicate_tag?(tag_name) + @project.tag_list.split.include?(tag_name) + end + def find_tagging tag = Tag.find_by(name: params[:id]) raise ParamRecordNotFound if tag.nil? diff --git a/app/decorators/analysis/chart.rb b/app/decorators/analysis/chart.rb index 193134c21..1e3b7015b 100644 --- a/app/decorators/analysis/chart.rb +++ b/app/decorators/analysis/chart.rb @@ -18,7 +18,7 @@ def series_and_range_data(default_options) end def first_ticks - series.first.ticks + series.first&.ticks end def min_month_as_ticks diff --git a/app/decorators/analysis/code_history_chart.rb b/app/decorators/analysis/code_history_chart.rb index 741ecfb2f..3894b3666 100644 --- a/app/decorators/analysis/code_history_chart.rb +++ b/app/decorators/analysis/code_history_chart.rb @@ -9,13 +9,12 @@ def initialize(analysis) end def data - series_and_range_data(@defaults).deep_merge(chart_watermark) + series_and_range_data(@defaults) end def data_for_lines_of_code data.deep_merge(ANALYSIS_CHARTS_OPTIONS['no_auxillaries']) .deep_merge(ANALYSIS_CHARTS_OPTIONS['lines_of_code']) - .deep_merge(chart_watermark) end private diff --git a/app/decorators/analysis/commit_history_chart.rb b/app/decorators/analysis/commit_history_chart.rb index 942eadb86..3094a2c25 100644 --- a/app/decorators/analysis/commit_history_chart.rb +++ b/app/decorators/analysis/commit_history_chart.rb @@ -9,9 +9,11 @@ def initialize(analysis) end def data - series_and_range_data(@defaults) - .deep_merge(ANALYSIS_CHARTS_OPTIONS['commits_history_auxillaries']) - .deep_merge(chart_watermark) + chart_data = series_and_range_data(@defaults) + .deep_merge(ANALYSIS_CHARTS_OPTIONS['commits_history_auxillaries']) + .deep_merge(chart_watermark) + + apply_commits_palette(chart_data) end private @@ -25,6 +27,15 @@ def series_data_without_axis_data end def x_and_y_axis_data + return [] unless series.last + [{ 'x' => series.last.ticks, 'y' => series.last.commits }] end + + def apply_commits_palette(chart_data) + series_config = chart_data[:series] || [] + series_config[0][:color] = '#2f7ed8' if series_config[0] + series_config[1][:color] = '#2f7ed8' if series_config[1] + chart_data + end end diff --git a/app/decorators/analysis/contributor_history_chart.rb b/app/decorators/analysis/contributor_history_chart.rb index d93c7bb05..e97911ba3 100644 --- a/app/decorators/analysis/contributor_history_chart.rb +++ b/app/decorators/analysis/contributor_history_chart.rb @@ -27,6 +27,8 @@ def series_data_without_axis_data end def x_and_y_axis_data + return [] unless series.last + [{ 'x' => series.last.ticks, 'y' => series.last.contributors }] end end diff --git a/app/decorators/baseball_card.rb b/app/decorators/baseball_card.rb index 40b4997c2..4eb3a4f45 100644 --- a/app/decorators/baseball_card.rb +++ b/app/decorators/baseball_card.rb @@ -41,7 +41,8 @@ def last_checkin def commits return if best_account_analysis.nil? - { label: i18n('commits.label'), + { css: { class: 'statistic--commits' }, + label: i18n('commits.label'), value: i18n('commits.value', count: account_analysis_fact.commits) } end @@ -62,7 +63,7 @@ def orgs orgs_for_positions = organization_core.orgs_for_my_positions return if orgs_for_positions.empty? - { css: { style: 'min-height:38px;' }, + { css: { class: 'statistic--orgs', style: 'min-height:38px;' }, label: i18n('contributed_to'), partial: 'accounts/show/orgs', locals: { orgs: orgs_for_positions } } @@ -72,7 +73,7 @@ def affiliations affiliated_orgs = organization_core.affiliations_for_my_positions return if affiliated_orgs.empty? - { css: { style: 'min-height:38px;' }, + { css: { class: 'statistic--orgs', style: 'min-height:38px;' }, label: i18n('contributed_for'), partial: 'accounts/show/orgs', locals: { orgs: affiliated_orgs } } diff --git a/app/decorators/chart_decorator.rb b/app/decorators/chart_decorator.rb index c0a231fa7..321e8352f 100644 --- a/app/decorators/chart_decorator.rb +++ b/app/decorators/chart_decorator.rb @@ -53,8 +53,15 @@ def string_to_hash(stringified_dates) def build_chart_with_background_style_and_commit_history chart = CHART_DEFAULTS.clone - chart.deep_merge!(background_style('watermark_white_900')) chart.deep_merge! YAML.load_file Rails.root.join('config', 'charting', 'project_commit_history.yml') + strip_background_styles(chart) + chart + end + + def strip_background_styles(chart) + %w[background-image background-repeat background-position backgroundColor].each do |key| + chart['chart']['style'].delete(key) + end end end diff --git a/app/decorators/icon.rb b/app/decorators/icon.rb index ea423e418..64dc77e80 100644 --- a/app/decorators/icon.rb +++ b/app/decorators/icon.rb @@ -9,18 +9,31 @@ class Icon < Cherry::Decorator delegate :logo, :name, to: :object - def image(with_dimensions: true) - if logo - width_and_height = with_dimensions ? dimensions : '' - css_style = "#{width_and_height} border:0 none;" - image_tag(logo.attachment.url(size), style: css_style, itemprop: 'image', alt: 'img avatar') - else - content_tag :p, name.first.capitalize, style: default_style - end + def image(with_dimensions: true, container_class: 'icon-container') + has_attachment = logo&.attachment&.file? + container_classes = has_attachment ? "#{container_class} has-logo" : container_class + content = has_attachment ? logo_with_fallback(with_dimensions) : letter_only + content_tag :div, content, class: container_classes end private + def logo_with_fallback(with_dimensions) + css_style = "#{dimensions if with_dimensions} border:0 none;" + onerror_handler = "this.style.display='none'; this.nextElementSibling.style.display='flex'; " \ + "this.parentElement.classList.remove('has-logo');" + img = image_tag(logo.attachment.url(size), + style: css_style, + itemprop: 'image', + alt: name, + onerror: onerror_handler) + img + content_tag(:span, name.first.upcase, class: 'icon-letter', style: 'display:none') + end + + def letter_only + content_tag :span, name.first.upcase, class: 'icon-letter' + end + def size @context[:size] || :small end diff --git a/app/decorators/organization_decorator.rb b/app/decorators/organization_decorator.rb index 4dd8ba2ae..7d3de56d0 100644 --- a/app/decorators/organization_decorator.rb +++ b/app/decorators/organization_decorator.rb @@ -16,8 +16,9 @@ def sidebar ] end - def icon(size = :small, opts = {}) - Icon.new(organization, context: { size: size, options: opts }).image(with_dimensions: false) + def icon(size = :small, container_class: 'icon-container', **opts) + Icon.new(organization, context: { size: size, options: opts }).image(with_dimensions: false, + container_class: container_class) end class << self diff --git a/app/decorators/project/demographic_chart.rb b/app/decorators/project/demographic_chart.rb index ba058a06c..d240a1e56 100644 --- a/app/decorators/project/demographic_chart.rb +++ b/app/decorators/project/demographic_chart.rb @@ -15,9 +15,10 @@ def data private def activity_level_data - level_data = count_by_activity_level.each_with_object([]) do |(level, count), array| - array.push Project::ActivityLevelIndex.new(level, count, total_count).demographic_chart_data - end + level_data = Project::ActivityLevelIndex::ACTIVITY_LEVEL_INDEX.map do |level, _| + count = count_by_activity_level[level].to_i + Project::ActivityLevelIndex.new(level, count, [total_count, 1].max).demographic_chart_data if count.positive? + end.compact level_data.sort_by { |d| SORT_ORDER.index(d[:name]) } end diff --git a/app/decorators/project_decorator.rb b/app/decorators/project_decorator.rb index ac9436238..766717280 100644 --- a/app/decorators/project_decorator.rb +++ b/app/decorators/project_decorator.rb @@ -5,12 +5,12 @@ class ProjectDecorator < Cherry::Decorator delegate :main_language, :links, to: :project - def icon(size = :small, opts = {}) + def icon(size = :small, container_class: 'icon-container', **opts) opts[:color] = language_text_color(main_language) opts[:bg] = language_color(main_language) icon = Icon.new(project, context: { size: size, options: opts }) - icon.image + icon.image(with_dimensions: true, container_class: container_class) end def sorted_link_list diff --git a/app/decorators/vulnerability/version_chart.rb b/app/decorators/vulnerability/version_chart.rb index dcaea58bd..aa2706ae3 100644 --- a/app/decorators/vulnerability/version_chart.rb +++ b/app/decorators/vulnerability/version_chart.rb @@ -27,7 +27,7 @@ def set_xaxis end def series - color_map = ['#0c1c33', '#183867', '#4b6b9a', '#a5b5cd', '#cce1e9'] + color_map = ['#4a148c', '#7b1fa2', '#9c27b0', '#ba68c8', '#ce93d8'] SEVERITY_LEGENDS.keys.each_with_index do |level, index| legend = SEVERITY_LEGENDS[level] @default_options[:series][index] = { name: "CVE #{legend[0]}", @@ -41,7 +41,7 @@ def series end def bdsa_series - bdsa_color_map = ['rgb(90,16,12)', 'rgb(156,37,31)', 'rgb(231,140,135)', 'rgb(154,155,156)', '#D3D3D3'] + bdsa_color_map = ['#8b0000', '#c62828', '#f57c00', '#fbc02d', '#9e9e9e'] SEVERITY_LEGENDS.keys.each_with_index do |level, index| legend = SEVERITY_LEGENDS[level] @default_options[:series][index + 5] = { name: "BDSA #{legend[0]}", diff --git a/app/helpers/avatar_helper.rb b/app/helpers/avatar_helper.rb index f13545076..acf1e8d2b 100644 --- a/app/helpers/avatar_helper.rb +++ b/app/helpers/avatar_helper.rb @@ -20,7 +20,8 @@ def avatar_img_path(who, size = 32) def avatar_img_for(who, size = 32) return '' unless who - image_tag avatar_img_path(who, size), style: "width: #{size}px; height: #{size}px;", class: 'avatar' + image_tag avatar_img_path(who, size), style: "width: #{size}px; height: #{size}px;", class: 'avatar', + alt: avatar_title(who) end def avatar_path(who) diff --git a/app/helpers/kudos_helper.rb b/app/helpers/kudos_helper.rb index dd5229c15..0200425ba 100644 --- a/app/helpers/kudos_helper.rb +++ b/app/helpers/kudos_helper.rb @@ -43,9 +43,11 @@ def kudo_delete_link(kudo) confirm = kudo_delete_link_confirm(kudo) return nil unless confirm - haml_tag :a, href: kudo_path(kudo), class: 'command btn btn-minier btn-primary', + haml_tag :a, href: kudo_path(kudo), class: 'btn kudo-btn btn-mini', data: { method: :delete, confirm: confirm } do - haml_tag :i, '', class: 'icon-undo rescind-kudos' + haml_tag :i, class: 'rescind-kudos' do + haml_concat undo_svg_icon + end haml_tag :span, I18n.t('kudos.undo') end end @@ -80,6 +82,31 @@ def kudo_rank_from_kudo(kudo) private + def kudos_button_path(target) + if target.is_a?(Account) + new_kudo_path(account_id: target.to_param) + else + new_kudo_path(contribution_id: target.id) + end + end + + def undo_svg_icon + svg_attrs = 'class="kudo-btn-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" ' \ + 'fill="none" stroke="currentColor" stroke-width="2" ' \ + 'stroke-linecap="round" stroke-linejoin="round"' + "" \ + ''.html_safe + end + + def thumbs_up_svg_icon + svg_attrs = 'class="kudo-btn-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" ' \ + 'fill="none" stroke="currentColor" stroke-width="2" ' \ + 'stroke-linecap="round" stroke-linejoin="round"' + path1 = 'M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3z' + path2 = 'M7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3' + "".html_safe + end + def kudo_button_target_account(target) case target when Account @@ -106,14 +133,13 @@ def remove_kudos_button(kudo) def give_kudos_button(target) label = I18n.t('kudos.give_kudos_to', name: kudo_target_name(target)) - path = if target.is_a?(Account) - new_kudo_path(account_id: target.to_param) - else - new_kudo_path(contribution_id: target.id) - end + path = kudos_button_path(target) haml_tag :div do - haml_tag :a, I18n.t('kudos.give_kudos'), href: '#', class: 'btn kudo-btn btn-primary btn-mini', - onclick: "tb_show('#{label}', '#{path}', false); return false;" + haml_tag :a, href: '#', class: 'btn kudo-btn btn-primary btn-mini', + onclick: "tb_show('#{label}', '#{path}', false); return false;" do + haml_concat thumbs_up_svg_icon + haml_concat I18n.t('kudos.give_kudos') + end end end diff --git a/app/helpers/managers_helper.rb b/app/helpers/managers_helper.rb index ef28f5275..d1ff7a16a 100644 --- a/app/helpers/managers_helper.rb +++ b/app/helpers/managers_helper.rb @@ -32,37 +32,43 @@ def reject_parent_manager_path(obj, account) def edit_manages_button(parent, account) url = edit_parent_manager_path(parent, account.to_param) - icon_button(url, size: 'small', type: 'success', icon: 'pencil', text: t('managers.manage.edit')) + link_to url, class: 'btn btn-md btn-primary' do + content_tag(:i, '', class: 'icon-pencil') + " #{t('managers.manage.edit')}" + end end def withdraw_manages_button(parent, account, name, target) url = reject_parent_manager_path(parent, account.to_param) - confirm = t('managers.manage.confirm_withdraw', name: name, target: target) - title = t('managers.manage.withdraw_title', name: name, target: target) - icon_button(url, size: 'small', type: 'danger', icon: 'trash', method: :post, - text: t('managers.manage.withdraw'), data: { confirm: confirm }, title: title) + link_to url, method: :post, class: 'btn btn-sm btn-danger', + title: t('managers.manage.withdraw_title', name: name, target: target), + data: { confirm: t('managers.manage.confirm_withdraw', name: name, target: target) } do + content_tag(:i, '', class: 'icon-trash') + " #{t('managers.manage.withdraw')}" + end end def approve_manages_button(parent, account, name, target) url = approve_parent_manager_path(parent, account.to_param) - confirm = t('managers.manage.confirm_approve', name: name, target: target) - title = t('managers.manage.approve_title', name: name, target: target) - icon_button(url, size: 'small', type: 'success', icon: 'thumbs-up', method: :post, - text: t('managers.manage.approve'), data: { confirm: confirm }, title: title) + link_to url, method: :post, class: 'btn btn-sm btn-primary', + title: t('managers.manage.approve_title', name: name, target: target), + data: { confirm: t('managers.manage.confirm_approve', name: name, target: target) } do + content_tag(:i, '', class: 'icon-thumbs-up') + " #{t('managers.manage.approve')}" + end end def reject_manages_button(parent, account, name, target) url = reject_parent_manager_path(parent, account.to_param) - confirm = t('managers.manage.confirm_reject', name: name, target: target) - title = t('managers.manage.reject_title', name: name, target: target) - icon_button(url, size: 'small', type: 'danger', icon: 'thumbs-down', method: :post, - text: t('managers.manage.reject'), data: { confirm: confirm }, title: title) + link_to url, method: :post, class: 'btn btn-sm btn-danger', + title: t('managers.manage.reject_title', name: name, target: target), + data: { confirm: t('managers.manage.confirm_reject', name: name, target: target) } do + content_tag(:i, '', class: 'icon-thumbs-down') + " #{t('managers.manage.reject')}" + end end def remove_manages_button(parent, account, name, target) url = reject_parent_manager_path(parent, account.to_param) - confirm = t('managers.manage.confirm_delete', name: name, target: target) - icon_button(url, size: 'small', type: 'danger', icon: 'trash', method: :post, - text: t('managers.manage.remove_manager'), data: { confirm: confirm }) + link_to url, method: :post, class: 'btn btn-md btn-danger', + data: { confirm: t('managers.manage.confirm_delete', name: name, target: target) } do + content_tag(:i, '', class: 'icon-trash') + " #{t('managers.manage.remove_manager')}" + end end end diff --git a/app/helpers/page_context_helper.rb b/app/helpers/page_context_helper.rb index c09eb0c9c..cad866bb0 100644 --- a/app/helpers/page_context_helper.rb +++ b/app/helpers/page_context_helper.rb @@ -10,7 +10,7 @@ def account_context set_page_context(footer_menu_list: @account.decorate.sidebar_for(current_user), select_footer_nav: :account_summary, select_top_menu_nav: :select_people, - page_header: 'accounts/mini_header') + page_header: 'accounts/show/header') end def organization_context diff --git a/app/helpers/ratings_helper.rb b/app/helpers/ratings_helper.rb index 4cfaf0858..bf4ed5fd9 100644 --- a/app/helpers/ratings_helper.rb +++ b/app/helpers/ratings_helper.rb @@ -1,54 +1,54 @@ # frozen_string_literal: true module RatingsHelper + STAR_PATH = 'M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 ' \ + '1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034 ' \ + 'a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 ' \ + '8.72c-.783-.57-.381-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z' + def rating_stars(id, score, mini: false) - dim = rating_star_dimensions(mini) - width = ((score.to_f * (dim[:max] - dim[:min]) / 5.0) + 0.5 + dim[:min]).to_i - rating_star_html(id, score, dim, width) + " \ + #{rating_star_schema(score)} \ + #{svg_star_rating(score, mini: mini)} \ + " end private - def rating_star_html(id, score, dim, width) - return rating_star_worst(id, score, dim) if width <= dim[:min] - return rating_star_best(id, score, dim) if width >= dim[:max] - - rstarfull(id, score, "#{rating_star_row(dim, width, false)} #{rating_star_row(dim, dim[:max] - width, true)}") - end - - def rating_star_worst(id, score, dim) - rstarfull(id, score, rating_star_row(dim, dim[:max], true)) + def svg_star_rating(score, mini: false) + stars_html = (1..5).map { |star| build_star_for_rating(star, score, mini) }.join + "#{stars_html}" end - def rating_star_best(id, score, dim) - rstarfull(id, score, rating_star_row(dim, dim[:max], false)) + def build_star_for_rating(star, score, mini) + if star <= score.to_f + build_star_svg('#ffb91a', mini: mini) + elsif (star - 0.5) <= score.to_f + build_half_star_svg(mini: mini) + else + build_star_svg('#d1d5db', mini: mini) + end end - def rating_star_dimensions(mini) - return { max: 76, min: 1, height: 17, img: 'stars_sprite_mini.png' } if mini - - { max: 112, min: 2, height: 23, img: 'stars_sprite.png' } + def build_star_svg(color, mini: false) + size = mini ? '12px' : '14px' + %( ) end - def rstarfull(id, score, ratings) - " \ - #{rating_star_schema(score)} #{ratings} \ - " + def build_half_star_svg(mini: false) + size = mini ? '12px' : '14px' + gradient_id = "half-star-#{SecureRandom.hex(4)}" + " " \ + ' ' \ + ' ' \ + " " end def rating_star_schema(score) - <<-HTML -
- #{score} -
- HTML - end - - def rating_star_row(dimensions, width, empty) - position = empty ? 'right top' : 'left bottom' - " \ -   \ - " + '
' \ + "#{score}
" end end diff --git a/app/helpers/site_features_helper.rb b/app/helpers/site_features_helper.rb index ecee0ec49..3a0812fde 100644 --- a/app/helpers/site_features_helper.rb +++ b/app/helpers/site_features_helper.rb @@ -6,10 +6,11 @@ module SiteFeaturesHelper def features_hash { 'OpenHub' => [ - "you can subscribe to e-mail newsletters to receive update from the Open Hub blog", + "you can subscribe to e-mail newsletters to receive update from the Open Hub blog", "data presented on the Open Hub is available through our - API", + API", "you can embed statistics from Open Hub on your site", 'by exploring contributors within projects, you can view details on every commit @@ -18,8 +19,9 @@ def features_hash "compare projects before you chose one to use", "check out hot projects on the Open Hub", "anyone with an Open Hub account can update a project's tags", - "learn about Open Hub updates and features on the - Open Hub blog" + 'learn about Open Hub updates and features on the ' \ + "Open Hub blog" ], 'Security' => [ diff --git a/app/lib/acts_as_taggable/acts_as_taggable.rb b/app/lib/acts_as_taggable/acts_as_taggable.rb index 41388fe54..9c06e3176 100644 --- a/app/lib/acts_as_taggable/acts_as_taggable.rb +++ b/app/lib/acts_as_taggable/acts_as_taggable.rb @@ -19,7 +19,7 @@ def tag_list=(list) return if tag_list == list self.tag_list_is_dirty = true - self.tags = parse_tag_list(list).map { |tag| Tag.where(name: tag).first_or_create } + self.tags = parse_tag_list(list).uniq.map { |tag| Tag.where(name: tag).first_or_create } end def tag_list diff --git a/app/models/account.rb b/app/models/account.rb index 9a24ee4eb..df1888659 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -135,6 +135,19 @@ def project_core @project_core ||= Account::ProjectCore.new(self) end + def theme_preference + setting = Setting.find_by(key: "account_#{id}_theme_preference") + setting&.value + end + + def theme_preference=(theme) + if theme == 'dark' + Setting.find_or_create_by(key: "account_#{id}_theme_preference").update(value: 'dark') + else + Setting.find_by(key: "account_#{id}_theme_preference")&.destroy + end + end + class << self def resolve_login(login) Account.find_by('lower(login) = ?', login.to_s.downcase) diff --git a/app/views/abouts/tools.html.haml b/app/views/abouts/tools.html.haml index 1200ebfc1..874ea44a8 100644 --- a/app/views/abouts/tools.html.haml +++ b/app/views/abouts/tools.html.haml @@ -1,53 +1,118 @@ - content_for(:html_title) do = t('.openhub_tools') -.col-md-12 - %h1= t('.tools') - %br - .row - .col-md-4 - %h3= link_to t('.compare_projects'), compare_projects_path - %p= t('.compare_stats') - = link_to((image_tag 'tools/compare_projects_preview_graph.png', alt: t('.compare_projects')), - compare_projects_path) - - .col-md-4 - %h3= link_to t('.compare_languages'), compare_languages_path - %p= t('.compare_languages_stats') - = link_to((image_tag 'tools/compare_languages_preview_graph.png', alt: t('.compare_languages')), - compare_languages_path) - - .col-md-4 - %h3= link_to t('.compare_repositories'), compare_repositories_path - %p= t('.compare_repositories_stats') - = link_to((image_tag 'tools/compare_repositories_preview_graph.png', alt: t('.compare_repositories')), - compare_repositories_path) - - .row - %p   - .row - .col-md-8 - %h3= link_to t('.language_stats'), languages_path - %p= t('.view_stats_per_language') - %p= t('.font_size') - %p.language-list-lineheight - - @languages.each do |language| - = link_to language.nice_name, language_path(language), style: compute_style_for_language_tag(language) - %p= t('.view_stats_for_all_languages_html', href: link_to(t('.all_languages'), languages_path)) - .col-md-4 - %h3= t('.additional_info') - %h4= t('.api') - %ul.margin_left_30 - %li - = link_to t('.getting_started'), 'https://github.com/blackducksoftware/ohloh_api#ohloh-api-documentation' - %li - = link_to t('.examples'), - 'https://github.com/blackducksoftware/ohloh_api/tree/main/examples#ohloh-api-examples' - %h4= t('.open_source_projects') - %ul.margin_left_30 - %li - = link_to t('.ohcount'), 'https://github.com/blackducksoftware/ohcount' - %span= t('.line_counter_metrics') - %li - = link_to t('.ohloh_scm'), 'https://github.com/blackducksoftware/ohloh_scm' - %span= t('.source_code_abstraction') +.tools-page + / Header + .tools-header + .container + %h1.tools-title= t('.tools') + + / Main Content + .tools-content + .container + / Comparison Tools Grid + .tools-grid + / Compare Projects Card + .tool-card.tool-card-projects + .tool-card-header + .tool-icon.tool-icon-orange + %i.icon-code-fork + %h2.tool-card-title + = link_to t('.compare_projects'), compare_projects_path + .tool-card-body + %p.tool-description= t('.compare_stats') + = link_to(image_tag('tools/compare_projects_preview_graph.png', alt: t('.compare_projects'), class: 'chart-image'), compare_projects_path) + + / Compare Languages Card + .tool-card.tool-card-languages + .tool-card-header + .tool-icon.tool-icon-blue + %i.icon-code + %h2.tool-card-title + = link_to t('.compare_languages'), compare_languages_path + .tool-card-body + %p.tool-description= t('.compare_languages_stats') + = link_to(image_tag('tools/compare_languages_preview_graph.png', alt: t('.compare_languages'), class: 'chart-image'), compare_languages_path) + + / Compare Repositories Card + .tool-card.tool-card-repositories + .tool-card-header + .tool-icon.tool-icon-green + %i.icon-bar-chart + %h2.tool-card-title + = link_to t('.compare_repositories'), compare_repositories_path + .tool-card-body + %p.tool-description= t('.compare_repositories_stats') + = link_to(image_tag('tools/compare_repositories_preview_graph.png', alt: t('.compare_repositories'), class: 'chart-image'), compare_repositories_path) + + / Two Column Layout + .tools-bottom-grid + / Language Statistics (Left Column) + .language-stats-section + .section-header + .section-header-content + %h2.section-title + = link_to t('.language_stats'), languages_path + %p.section-subtitle= t('.language_stats_subtitle') + %i.icon-bar-chart.section-icon + + .section-content + .info-box + %p.info-text + = t('.view_stats_per_language') + .info-badge + %i.icon-signal + %span= t('.font_size') + + / Language Cloud + .language-cloud-wrapper + .language-cloud + - @languages.each do |language| + = link_to language.nice_name, language_path(language), style: compute_style_for_language_tag(language), class: 'language-tag' + + / Footer CTA + .language-cta + .cta-text + %i.icon-code + %span= t('.explore_language_metrics') + = link_to languages_path, class: 'cta-button' do + = t('.view_all_languages') + %i.icon-arrow-right + + / Additional Information (Right Column) + .additional-info-section + .section-header-simple + %h2.section-title= t('.additional_info') + + .section-content + / API Section + .info-card.info-card-api + .info-card-header + .info-card-icon.info-icon-blue + %i.icon-code + %h3.info-card-title= t('.api') + %ul.info-list + %li + %span.list-bullet • + = link_to t('.getting_started'), 'https://github.com/blackducksoftware/ohloh_api#ohloh-api-documentation', target: '_blank', rel: 'noopener noreferrer' + %li + %span.list-bullet • + = link_to t('.examples'), 'https://github.com/blackducksoftware/ohloh_api/tree/main/examples#ohloh-api-examples', target: '_blank', rel: 'noopener noreferrer' + + / Open Source Projects Section + .info-card.info-card-projects + .info-card-header + .info-card-icon.info-icon-green + %i.icon-code-fork + %h3.info-card-title= t('.open_source_projects') + %ul.info-list + %li + %span.list-bullet • + .list-content + = link_to t('.ohcount'), 'https://github.com/blackducksoftware/ohcount', target: '_blank', rel: 'noopener noreferrer', class: 'list-link' + %span= t('.line_counter_metrics') + %li + %span.list-bullet • + .list-content + = link_to t('.ohloh_scm'), 'https://github.com/blackducksoftware/ohloh_scm', target: '_blank', rel: 'noopener noreferrer', class: 'list-link' + %span= t('.source_code_abstraction') diff --git a/app/views/account_widgets/_widget.html.haml b/app/views/account_widgets/_widget.html.haml index beb2c04b3..05650729c 100644 --- a/app/views/account_widgets/_widget.html.haml +++ b/app/views/account_widgets/_widget.html.haml @@ -3,7 +3,7 @@ widget_html = render(template: "#{controller_name}/#{widget.name.underscore}", locals: locals) .widget_list - .row{ style: 'margin-left: -15px' } + .row.oh-card{ style: 'margin-left: -15px' } .col-md-10 %h3.inset_top.no_padding_top = widget.short_nice_name @@ -18,5 +18,5 @@ %h6= t('.bbc_code') %p %span.soft= t('.bbc') - %pre.prettyprint + %pre.prettyprint.oh-card = h render(template: "#{controller_name}/#{widget.name.underscore}", locals: locals.merge(bbc: true)) diff --git a/app/views/accounts/_account.html.haml b/app/views/accounts/_account.html.haml index ba95d19a3..9a069b657 100644 --- a/app/views/accounts/_account.html.haml +++ b/app/views/accounts/_account.html.haml @@ -3,73 +3,68 @@ pos_ids, more_pos_count = @cbp_map[account.id] sorted_cbl = account.decorate.sorted_commits_by_language -.well.well-sm.no_padding - .pull-left.avatar_new - = avatar_for account, size: 128 - - .pull-left.account_details - %h3 - = link_to h(truncate(account.name, length: 18)), account_path(account), class: 'margin_top_20', - title: h(account.name) - %p.small.word_break_all= truncate(account.markup.try(:first_line), length: 75) - .small.member_info - - if account.projects.any? - - manages_count_text = t 'people.claimed_person.manages', what: pluralize(account.projects.count, 'project') - %p= link_to manages_count_text, account_projects_path(account) - %p= t '.member_since', since: account.created_at.strftime('%B %Y') - - if only_device? - .pull-left.kudo_rank.signature_color - = account.kudo_rank - - .mini-badges-section.pull-left.kudo_badge - = render 'badges_row', badges: [BadgeDecorator.new(Badge::KudoRankBadge.new(account))] - .pull-left.commits_summary - - if vf.commits > 0 && @positions_map.any? - .commits_count.pull-left - = vf.commits - - .text-center - = link_to pluralize_without_count(vf.commits, 'Commit'), account_positions_path(account) - .pull-left.in= t('.in') - - if sorted_cbl.empty? - .pull-left.language_summary.no_lang_summary - = t('.no_lang_summary') - - else - .pull-left.language_summary - .pull-left{ style: 'min-height: 99px' } - - sorted_cbl.first(3).each do |lang, lang_hash| - .language_name{ style: "background-color:##{language_color(lang)}; color: ##{language_text_color(lang)}" } - %span= link_to lang_hash[:nice_name], language_path(lang) - .pull-left.other_language +.people-card + .people-card__body + .people-card__row + .people-card__avatar-info + = avatar_for account, size: 96 + .people-card__info + %h3.people-card__name + = link_to h(truncate(account.name, length: 18)), account_path(account), title: h(account.name) + %p.people-card__meta.word_break_all= truncate(account.markup.try(:first_line), length: 75) + - if account.projects.any? + - manages_count_text = t 'people.claimed_person.manages', what: pluralize(account.projects.count, 'project') + %p.people-card__meta= link_to manages_count_text, account_projects_path(account) + %p.people-card__meta= t '.member_since', since: account.created_at.strftime('%B %Y') + .people-card__commits + - if vf.commits > 0 + .people-card__commits-count= vf.commits + .people-card__commits-label + = link_to pluralize_without_count(vf.commits, 'Commit'), account_positions_path(account) + - else + .people-card__commits-count 0 + .people-card__commits-label Commits + .people-card__activity + - if vf.commits > 0 && @positions_map.any? + .people-card__row-langs + %span.people-card__label= t('.in') + .people-card__lang-tags + - if sorted_cbl.empty? + %span.people-card__no-lang= t('.no_lang_summary') + - else + - sorted_cbl.first(3).each_with_index do |(lang, lang_hash), i| + - if i == 0 + %span.people-card__lang-primary + = link_to lang_hash[:nice_name], language_path(lang) + - else + %span.people-card__lang-secondary + = link_to lang_hash[:nice_name], language_path(lang) + .people-card__row-projects + %span.people-card__label= t('.to') + .people-card__project-list + - pos_ids.each do |pos_id| + - project = @positions_map[pos_id].try(:project) + - next unless project + .people-card__project-item + = project.decorate.icon(:small, width: 20) + = link_to h(truncate(project.name, length: 10)), project_path(project), class: 'people-card__project-name' + %p.people-card__other-links - if sorted_cbl.length > 3 - other_langs_count = sorted_cbl.length - 3 - pluaral_term = pluralize_without_count(other_langs_count, 'language') - langs_link = t('.langs_link_text1', other_langs_count: other_langs_count, pluaral_term: pluaral_term) - = link_to langs_link, account_languages_path(account) + = link_to langs_link, account_languages_path(account), class: 'people-card__link' - else - = link_to t('.langs_link_text2'), account_languages_path(account) - - .pull-left.in= t('.to') - .pull-left.project_summary - - pos_ids.each do |pos_id| - - project = @positions_map[pos_id].try(:project) - - next unless project - .margin_bottom_10 - = project.decorate.icon(:small, width: 24) - = link_to h(truncate(project.name, length: 10)), project_path(project), class: 'margin_left_5' - .pull-left.other_language.other_projects - - if more_pos_count > 0 - - pluaral_term1 = pluralize_without_count(other_langs_count, 'project') - = link_to t('.positions_link_text1', more_pos_count: more_pos_count, pluaral_term: pluaral_term1), - account_positions_path(account), class: 'more_commits_padding' + = link_to t('.langs_link_text2'), account_languages_path(account), class: 'people-card__link' + %span.people-card__link-dot{ aria: { hidden: 'true' } } + - if more_pos_count > 0 + - pluaral_term1 = pluralize_without_count(more_pos_count, 'project') + = link_to t('.positions_link_text1', more_pos_count: more_pos_count, pluaral_term: pluaral_term1), account_positions_path(account), class: 'people-card__link' + - else + = link_to t('.positions_link_text2'), account_positions_path(account), class: 'people-card__link' - else - = link_to t('.positions_link_text2'), account_positions_path(account), class: 'more_commits_padding' - - else - %p.no_commits= t('.no_commits') - - unless only_device? - .pull-left.kudo_rank.signature_color - = account.kudo_rank - - .mini-badges-section.pull-left.kudo_badge - = render 'badges_row', badges: [BadgeDecorator.new(Badge::KudoRankBadge.new(account))] - .clear + %p.people-card__no-commits= t('.no_commits') + .people-card__kudos + .people-card__kudos-rank= account.kudo_rank + .people-card__kudos-badge + = render 'badges_row', badges: [BadgeDecorator.new(Badge::KudoRankBadge.new(account))] diff --git a/app/views/accounts/_fields.html.haml b/app/views/accounts/_fields.html.haml index 97fe93018..f3e40122c 100644 --- a/app/views/accounts/_fields.html.haml +++ b/app/views/accounts/_fields.html.haml @@ -1,39 +1,39 @@ -= form_for @account, html: { id: :account_form, class: 'well' } do |f| += form_for @account, html: { id: :account_form } do |f| %fieldset - %legend= t('.new_account') - .controls - %label.control-label.required= t('.login') - = f.text_field(:login, id: :login) + .signup-field-group + = f.label(:login, t('.login'), class: 'signup-field-label', for: 'login') + .signup-field-input-wrapper + %i.fa.fa-user.signup-field-icon + = f.text_field(:login, id: :login, class: 'signup-field-input', placeholder: 'Your login name') - error_tag(@account, :login) - %p.help-block - = t('.login_help') - .control-group - %label.control-label.required= t('email_address') - = f.text_field(:email, id: :email) + %p.signup-field-hint= t('.login_help') + + .signup-field-group + = f.label(:email, t('email_address'), class: 'signup-field-label', for: 'email') + .signup-field-input-wrapper + %i.fa.fa-envelope-o.signup-field-icon + = f.text_field(:email, id: :email, class: 'signup-field-input', placeholder: 'you@example.com') - error_tag(@account, :email) - %p.help-block + %p.signup-field-hint = t('.email_help_1') - %br - = t('.email_help_2') - %br - = t('.email_help_3') - = link_to t('gravatar'), 'https://gravatar.com', target: '_blank' - = t('.email_help_4') - .control-group - %label.control-label.required= t('password') - = f.password_field(:password, id: :password) + .signup-field-group + = f.label(:password, t('password'), class: 'signup-field-label', for: 'password') + .signup-field-input-wrapper + %i.fa.fa-lock.signup-field-icon + = f.password_field(:password, id: :password, class: 'signup-field-input signup-field-input--password', placeholder: 'Create a password') - error_tag(@account, :password) - %p.help-block= t('.password_help') + %p.signup-field-hint= t('.password_help') + / Honeypot — keep hidden for spam protection .control-group{ style: 'position: absolute; left: -9999px; opacity: 0;' } - %label Age (mandatory) + %label{ for: 'mandatory_age' } Age (mandatory) = text_field_tag :mandatory_age, nil, autocomplete: 'off', tabindex: '-1' = hidden_field_tag :visitor_id, Time.current.to_i - .control-group + .signup-field-group = render 'shared/captcha' - .actions - %input.btn.btn-primary{ type: :submit, value: t('.sign_up') } + .signup-field-actions + %input.signup-submit-btn{ type: :submit, value: t('.sign_up') } diff --git a/app/views/accounts/_join_now.html.haml b/app/views/accounts/_join_now.html.haml index 78ed3b488..c6c58c679 100644 --- a/app/views/accounts/_join_now.html.haml +++ b/app/views/accounts/_join_now.html.haml @@ -1,57 +1,14 @@ --if only_device? - #profile-description-2 - .text-2.show-more-height-2 - %p - = t('.description') - %ul#welcome-message - %li - = t('.welcome_bullet_1') - %li - = t('.welcome_bullet_2') - %li - = t('.welcome_bullet_3') - .show-more-2 Show More - %hr - #profile-description-1 - .text-1.show-more-height-1 - %h4 - %span.fa.fa-exclamation-triangle - = t('.please_note') - %p - = t('.creating_account_html', terms: link_to(t('.terms_of_use'), 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Terms-of-Use')) - %ul - %li - = t('.note_bullet_1') - %li - = t('.note_bullet_2') - %li - = t('.note_bullet_3') - %p - = t('.we_dont_like_disabling') - %span.fa.fa-smile-o - .show-more-1 Show More -- else - %ul#welcome-message - %li - = t('.welcome_bullet_1') - %li - = t('.welcome_bullet_2') - %li - = t('.welcome_bullet_3') - %hr - %h4 - %span.fa.fa-exclamation-triangle - = t('.please_note') - %p - = t('.creating_account_html', terms: link_to(t('.terms_of_use'), 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Terms-of-Use')) - %ul - %li - = t('.note_bullet_1') - %li - = t('.note_bullet_2') - %li - = t('.note_bullet_3') - %p - = t('.we_dont_like_disabling') - %span.fa.fa-smile-o +.signup-note-card + .signup-note-card__inner + %i.fa.fa-exclamation-triangle.signup-note-card__icon + .signup-note-card__body + %p.signup-note-card__title= t('.please_note') + %p.signup-note-card__text + = t('.creating_account_html', terms: link_to(t('.terms_of_use'), 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Terms-of-Use', target: '_blank', rel: 'noopener noreferrer')) +.signup-terms-note + By registering you agree to our + = link_to 'Terms of Service', 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Terms-of-Use', class: 'signup-terms-link', target: '_blank', rel: 'noopener noreferrer' + and + = link_to 'Privacy Policy', 'https://www.blackduck.com/privacy.html', class: 'signup-terms-link', target: '_blank', rel: 'noopener noreferrer' + \. diff --git a/app/views/accounts/_mini_header.html.haml b/app/views/accounts/_mini_header.html.haml index 8d49a576d..127f10b27 100644 --- a/app/views/accounts/_mini_header.html.haml +++ b/app/views/accounts/_mini_header.html.haml @@ -2,7 +2,7 @@ .margin_top_5#mini_account_row .col-md-1 = link_to account_path(@account) do - = image_tag avatar_img_path(@account, 62), size: '62x62', itemprop: 'image' + = image_tag avatar_img_path(@account, 62), size: '62x62', itemprop: 'image', alt: h(@account.name) - if lang - bg_color = language_color(lang.name) - text_color = language_text_color(lang.name) diff --git a/app/views/accounts/_welcome.html.haml b/app/views/accounts/_welcome.html.haml index da89fef66..592129cf6 100644 --- a/app/views/accounts/_welcome.html.haml +++ b/app/views/accounts/_welcome.html.haml @@ -1,7 +1 @@ -%h2 - = t('.title') --unless only_device? - %p - = t('.description') -.well#accounts-sign-up - = render 'join_now' +/ Welcome partial — content moved into new.html.haml signup-page layout diff --git a/app/views/accounts/edit.html.haml b/app/views/accounts/edit.html.haml index 833d94fcd..2da9c89d3 100644 --- a/app/views/accounts/edit.html.haml +++ b/app/views/accounts/edit.html.haml @@ -1,115 +1,177 @@ - page_context[:select_footer_nav] = :settings - content_for(:html_title) { t('accounts.edit.page_title', name: @account.name) } -.clearfix - - if @account.latitude && @account.longitude - content_for :javascript do - callback = "EditMap.jumpMeTo(#{@account.latitude}, #{@account.longitude});" = javascript_tag "$(document).on('page:change', function(){#{callback}});" #accounts_edits - .margin_left_15 - %h2 - = link_to t('settings'), settings_account_path(@account) - = t('.account_basics') - .well.margin_left_15#account-edit-form - = form_for @account do |f| - %fieldset.row-fluid - .col-md-5 - .form-group - %label.control-label.required= t('login') - .controls - = f.text_field :login, class: 'form-control check-availability', - 'data-original-value' => Account.find_by(id: @account).try(:login), - 'data-ajax-path' => account_check_availabilities_path, - 'data-preview-base-url' => accounts_url - = render 'shared/availability_preview' - - error_tag @account, :login - - .form-group - %label.control-label= t('name') - .controls - = f.text_field :name, class: 'form-control' - - error_tag @account, :name - - .form-group - %label.control-label.require= t('email_address') - .controls - = f.text_field :email, class: 'form-control' - - error_tag @account, :email - %p.help-block - = t('.email_help') - = link_to t('gravatar'), 'https://gravatar.com', target: '_blank' - - .form-group - %label.control-label= t('.homepage_url') - .controls - = f.text_field :url, class: 'form-control', value: @account.url - - error_tag @account, :url - - .form-group - %label.control-label= t('twitter_id') - .controls - = f.text_field :twitter_account, class: 'form-control' - - error_tag @account, :twitter_account - - .form-group - %label.control-label= t('affiliation') - .controls.chosen#value_select - = f.select :organization_id, OrganizationDecorator.select_options, {}, - class: 'value-select chzn-select form-control' - - error_tag @account, :organization_id - = f.text_field :organization_name, class: 'hidden margin_top_10 form-control' - = f.hidden_field :affiliation_type - - error_tag @account, :organization_name - %p.help-block{ style: 'margin-top: 6px' }= t('.affiliation_help') - - .col-md-5.col-md-push-1 - .form-group - %label.control-label!= t('.city_and_country') - .controls - %p.enabled - %input.form-control#location_scratch{ type: :text, autocomplete: :off, value: @account.location } - %a{ href: 'javascript:EditMap.clearLocation();' }= t('clear') - %p.error#not_found{ style: 'display:none' } - = t('.location_error_message') - #map - = f.hidden_field :location - = f.hidden_field :country_code - = f.hidden_field :latitude - = f.hidden_field :longitude - - .clearfix - - .form-group.margin_left_15 - %label.control-label= t('bio') - .control - - about_raw = @account.markup ? @account.markup.raw : '' - = f.text_area :about_raw, value: about_raw, class: 'edit-description', rows: '10' - - error_tag @account, 'markup.raw' - %p.help-block= t('.about_raw_help') - - .actions.col-md-10 - = submit_tag t('save_changes'), class: 'btn btn-primary pull-left margin_right_10' - = link_to t('.delete_account'), confirm_delete_account_path(@account), class: 'btn btn-danger pull-left' - -%h4.nomargin.margin_left_15 - %span.soft= t('about') - = t('basics') - -.col-md-5 - %ul - %li - = t('.help_1') - %li - = t('.help_2') - %b= t('.help_3') -.col-md-5 - %ul - %li - = t('.help_4') - %li - = t('.help_5') + .settings-page-header + .settings-page-header-row + .settings-page-icon + %i.fa.fa-user + %h2.settings-page-h2 + = link_to t('settings'), settings_account_path(@account) + %span.account-basics-sep + = t('.account_basics') + %p.settings-page-description= t('accounts.settings.account_basics_settings') + + = form_for @account, html: { id: 'account-edit-form' } do |f| + .edit-form-grid + + / ── Left column ────────────────────────────────────── + .edit-form-col + + .edit-section + .edit-section-header + .edit-section-icon + %i.fa.fa-id-card + %h3.edit-section-title Identity + + .edit-field-group + = f.label :login, t('login'), class: 'edit-field-label' + .edit-field-input-wrapper + %i.fa.fa-at.edit-field-icon + = f.text_field :login, class: 'edit-field-input check-availability', + placeholder: 'your_login', + 'data-original-value' => Account.find_by(id: @account).try(:login), + 'data-ajax-path' => account_check_availabilities_path, + 'data-preview-base-url' => accounts_url + = render 'shared/availability_preview' + - error_tag @account, :login + + .edit-field-group + = f.label :name, t('name'), class: 'edit-field-label' + .edit-field-input-wrapper + %i.fa.fa-user.edit-field-icon + = f.text_field :name, class: 'edit-field-input', placeholder: 'Display name' + - error_tag @account, :name + + .edit-section + .edit-section-header + .edit-section-icon + %i.fa.fa-envelope + %h3.edit-section-title Contact + + .edit-field-group + = f.label :email, t('email_address'), class: 'edit-field-label' + .edit-field-input-wrapper + %i.fa.fa-envelope-o.edit-field-icon + = f.text_field :email, class: 'edit-field-input', placeholder: 'you@example.com' + - error_tag @account, :email + %p.edit-field-hint + = t('.email_help') + = link_to t('gravatar'), 'https://gravatar.com', target: '_blank', rel: 'noopener noreferrer' + + .edit-field-group + = f.label :url, t('.homepage_url'), class: 'edit-field-label' + .edit-field-input-wrapper + %i.fa.fa-globe.edit-field-icon + = f.text_field :url, class: 'edit-field-input', value: @account.url, placeholder: 'https://' + - error_tag @account, :url + + .edit-field-group + = f.label :twitter_account, t('twitter_id'), class: 'edit-field-label' + .edit-field-input-wrapper + %i.fa.fa-twitter.edit-field-icon + = f.text_field :twitter_account, class: 'edit-field-input', placeholder: '@username' + - error_tag @account, :twitter_account + + .edit-section + .edit-section-header + .edit-section-icon + %i.fa.fa-building + %h3.edit-section-title= t('affiliation') + + .edit-field-group + = f.label :organization_id, t('affiliation'), class: 'edit-field-label' + .controls.chosen#value_select + = f.select :organization_id, OrganizationDecorator.select_options, {}, + class: 'value-select chzn-select form-control' + - error_tag @account, :organization_id + = f.text_field :organization_name, class: 'hidden margin_top_10 form-control', aria: { label: t('affiliation') } + = f.hidden_field :affiliation_type, aria: { label: t('affiliation') } + - error_tag @account, :organization_name + %p.edit-field-hint= t('.affiliation_help') + + / ── Right column ───────────────────────────────────── + .edit-form-col + + .edit-section + .edit-section-header + .edit-section-icon + %i.fa.fa-map-marker + %h3.edit-section-title= t('.city_and_country') + + .edit-field-group + %label.edit-field-label{ for: 'location_scratch' }!= t('.city_and_country') + .edit-field-input-wrapper + %i.fa.fa-map-marker.edit-field-icon + %input.edit-field-input#location_scratch{ type: :text, autocomplete: :off, value: @account.location, placeholder: 'City, Country' } + = link_to t('clear'), 'javascript:EditMap.clearLocation();', class: 'edit-clear-link' + %p.edit-field-error#not_found{ style: 'display:none' }= t('.location_error_message') + #map + = f.hidden_field :location + = f.hidden_field :country_code + = f.hidden_field :latitude + = f.hidden_field :longitude + + / ── Bio — full width ────────────────────────────────────── + .edit-section.edit-section--full + .edit-section-header + .edit-section-icon + %i.fa.fa-pencil + %h3.edit-section-title= t('bio') + + .edit-field-group + - about_raw = @account.markup ? @account.markup.raw : '' + = f.text_area :about_raw, value: about_raw, class: 'edit-field-textarea', rows: '6', + placeholder: t('.about_raw_help') + - error_tag @account, 'markup.raw' + %p.edit-field-hint= t('.about_raw_help') + + .edit-form-actions + = submit_tag t('save_changes'), class: 'edit-save-btn' + = link_to t('.delete_account'), confirm_delete_account_path(@account), class: 'edit-delete-btn' + +.about-account-basics-card + .card-header + .card-header-left + .card-header-icon + %i.fa.fa-info-circle + .card-header-text + %h4.card-title= t('about') + ' ' + t('basics') + %p.card-subtitle Tips for filling out your profile + %button.expand-toggle{ type: 'button', 'aria-label' => 'Toggle about section' } + %i.fa.fa-chevron-down + + .card-content + .tips-grid + .tip-item + .tip-icon + %i.fa.fa-tag + .tip-body + %p.tip-text= t('.help_1') + + .tip-item + .tip-icon + %i.fa.fa-envelope + .tip-body + %p.tip-text + = t('.help_2') + %strong= t('.help_3') + + .tip-item + .tip-icon + %i.fa.fa-link + .tip-body + %p.tip-text= t('.help_4') + + .tip-item + .tip-icon + %i.fa.fa-map-marker + .tip-body + %p.tip-text= t('.help_5') != map_init('map', @account.latitude ? 7 : 0) diff --git a/app/views/accounts/index.html.haml b/app/views/accounts/index.html.haml index 4bd342af1..14366a638 100644 --- a/app/views/accounts/index.html.haml +++ b/app/views/accounts/index.html.haml @@ -1,10 +1,20 @@ - content_for(:html_title) { t('.page_title') } - page_context[:select_top_menu_nav] = 'select_people' -%h1.margin_bottom_15= t '.people' -%h3.margin_bottom_15= t '.header' +.people-page-container + = link_to people_path, class: 'accounts-back-link' do + ← Back to contributors -= render partial: 'shared/search_dingus', locals: { collection: @people, sort_context: :people } -= render partial: 'account', collection: @people.map(&:account) + .people-page-header + %h1.people-page-title= t '.people' + = render 'shared/global_search', placeholder: t('shared.global_search.placeholder') -= will_paginate @people + %h2.people-section-title= t '.header' + + = render partial: 'shared/search_dingus', locals: { collection: @people, sort_context: :people } + + + = render partial: 'account', collection: @people.map(&:account) + + .people-pagination + = render 'shared/modern_pagination', collection: @people diff --git a/app/views/accounts/languages/_commits_by_language.html.haml b/app/views/accounts/languages/_commits_by_language.html.haml index 0dd3903bc..56286f9da 100644 --- a/app/views/accounts/languages/_commits_by_language.html.haml +++ b/app/views/accounts/languages/_commits_by_language.html.haml @@ -1,9 +1,9 @@ - chart_url = commits_by_language_account_charts_url(@account, scope: 'full').to_s +- light_wm = asset_url('charts/watermark_860.png') +- dark_wm = light_wm .margin_top_10 %h2.nomargin.col-md-2= t('.title') - .pull-right - = render partial: 'accounts/show/account_analysis_timestamp', locals: { best_account_analysis: @account.best_account_analysis } .clearfix .pull-right.chosen.languages#sort_by %select.chzn-select.language_category#streamgraph_category @@ -12,4 +12,4 @@ %option{ value: '1' }= t('.sort_option.one') %option{ value: '0' }= t('.sort_option.zero') .clearfix -.stream_graph.full.languages#ohloh_streamgraph{ 'datascope' => 'full', 'datasrc' => chart_url } +.stream_graph.full.languages#ohloh_streamgraph{ 'datascope' => 'full', 'datasrc' => chart_url, 'data-light-watermark' => light_wm, 'data-dark-watermark' => dark_wm } diff --git a/app/views/accounts/languages/index.html.haml b/app/views/accounts/languages/index.html.haml index 3b01ccd25..fccf0f569 100644 --- a/app/views/accounts/languages/index.html.haml +++ b/app/views/accounts/languages/index.html.haml @@ -3,15 +3,17 @@ page_context[:select_top_menu_nav] = 'select_people' page_context[:select_footer_nav] = :languages -- if @account.decorate.account_analysis_status_message.blank? - .col-md-12 - = render partial: 'accounts/languages/commits_by_language' - - if @vlfs.present? - .col-md-12#language_detail - %table.table.table-striped.table-condensed - %thead= render partial: 'contributions/language_header' - %tbody= render partial: 'contributions/language_fact', collection: @vlfs -- else - %h2= t('accounts.languages.index.title') - .col-md-12 - %p= @account.decorate.account_analysis_status_message +#accounts-languages-page + - if @account.decorate.account_analysis_status_message.blank? + .col-md-12 + = render partial: 'accounts/languages/commits_by_language' + - if @vlfs.present? + .col-md-12#language_detail + .table-responsive + %table.table.table-striped.table-condensed + %thead= render partial: 'contributions/language_header' + %tbody= render partial: 'contributions/language_fact', collection: @vlfs + - else + %h2= t('accounts.languages.index.title') + .col-md-12 + %p= @account.decorate.account_analysis_status_message diff --git a/app/views/accounts/new.html.haml b/app/views/accounts/new.html.haml index b7043a73d..0dea9ddf0 100644 --- a/app/views/accounts/new.html.haml +++ b/app/views/accounts/new.html.haml @@ -1,44 +1,101 @@ - content_for(:html_title) { t('.html_title') } +- page_context[:suppress_flash] = true -.col-xs-6.col-sm-7#welcome-message-container - = render 'welcome' - -- if @account.errors.none? - .col-xs-12.col-sm-5#sign-up-options - -if only_device? - .col-xs-3.pull-left#sign-up-git - = link_to 'javascript:', { class: 'btn btn-primary github-oauth' }.merge(github_data_attributes) do - = t('.sign_up_with') - %br - %i.fa.fa-github - Github - -else - .row-first - = link_to 'javascript:', { class: 'btn btn-primary btn-block github-oauth' }.merge(github_data_attributes) do - = t('.sign_up_with') +.signup-page + / ── Left Panel ────────────────────────────────────────── + .signup-left-panel + .signup-orb.signup-orb--top + .signup-orb.signup-orb--bottom + .signup-orb.signup-orb--mid + + .signup-left-content + .signup-badge + .signup-badge__dot + Free forever — no credit card required + + %h1.signup-headline + Join the %br - %i.fa.fa-github - Github - - %p.help-block - = t('.github_disclaimer') - -if only_device? - .col-xs-3.pull-right#sign-up-email - = link_to 'javascript:', id: 'email-sign-up', class: 'btn btn-primary' do - = t('.sign_up_with') - %br - %i.fa.fa-envelope-o - Email - -else - = link_to 'javascript:', id: 'email-sign-up', class: 'btn btn-primary btn-block' do - = t('.sign_up_with') + Open Source %br - %i.fa.fa-envelope-o - Email - -.col-xs-12.col-sm-5.no_right_padding{ id: ('sign-up-fields' if @account.errors.none?) } - - if flash[:message] - %p= flash[:message] - - unless @account.invite_code.blank? - %p= t('.invite_code_message') - = render 'fields' + %span.signup-headline__accent Community + + %p.signup-tagline= t('accounts.join_now.description') + + .signup-benefits + .signup-benefit + .signup-benefit__icon.signup-benefit__icon--amber + %i.fa.fa-code-fork + .signup-benefit__text + %p.signup-benefit__title Claim All Your Commits + %p.signup-benefit__desc= t('accounts.join_now.welcome_bullet_1') + + .signup-benefit + .signup-benefit__icon.signup-benefit__icon--purple + %i.fa.fa-trophy + .signup-benefit__text + %p.signup-benefit__title Gain Recognition + %p.signup-benefit__desc Build a verified developer profile with achievement badges and kudos. + + .signup-benefit + .signup-benefit__icon.signup-benefit__icon--green + %i.fa.fa-bar-chart + .signup-benefit__text + %p.signup-benefit__title Create Project Stacks + %p.signup-benefit__desc= t('accounts.join_now.welcome_bullet_3') + + .signup-benefit + .signup-benefit__icon.signup-benefit__icon--blue + %i.fa.fa-check-circle + .signup-benefit__text + %p.signup-benefit__title Manage Projects + %p.signup-benefit__desc Track, compare, and stay updated on the projects that matter to you. + + .signup-left-footer + %p.signup-disclaimer + OpenHub will only keep your GitHub email and login. Accounts created for advertising, link generation, or SEO will be disabled. + + / ── Right Panel ───────────────────────────────────────── + .signup-right-panel + = render partial: 'layouts/partials/alert' + .signup-form-card + / GitHub button + %button{ { type: 'button', class: 'btn-github github-oauth' }.merge(github_data_attributes) } + %i.fa.fa-github + = t('.sign_up_with') + GitHub + + %p.signup-github-note= t('.github_disclaimer') + + / Divider + .signup-divider + %span.signup-divider__line + %span.signup-divider__text or sign up with email + %span.signup-divider__line + + / Heading + .signup-form-header + %h2.signup-form-title Create your account + %p.signup-form-subtitle + Already have an account? + = link_to t('.login_text'), new_session_path, class: 'signup-signin-link' + + / Flash / invite messages + - if flash[:message] + .signup-flash= flash[:message] + - unless @account.invite_code.blank? + .signup-invite-msg= t('.invite_code_message') + + / Email toggle button (hidden when form has errors) + - if @account.errors.none? + #sign-up-options + %button#email-sign-up.btn-email-signup{ type: 'button' } + %i.fa.fa-envelope-o + Sign up with Email + + / Form fields — hidden on GET (id triggers CSS display:none), visible on POST errors (no id) + %div{ id: (@account.errors.none? ? 'sign-up-fields' : nil) } + = render 'fields' + + / Please Note + = render 'join_now' diff --git a/app/views/accounts/settings.html.haml b/app/views/accounts/settings.html.haml index 1596b897e..cd7c878a0 100644 --- a/app/views/accounts/settings.html.haml +++ b/app/views/accounts/settings.html.haml @@ -3,30 +3,40 @@ - account_context - page_context[:select_footer_nav] = :settings -.full-width.margin_left_20.margin_right_20 -.clear_left -.col-md-12 - %h3= t('.settings') - .account_settings_options - .account_setting_row - .pull-left.span5.settings_specifications - %h4= link_to t('.account_basics'), edit_account_path(@account) - %span.pull-left - %a.account_settings_icon.settings_account_basics{ href: edit_account_path(@account) } - %span= t('.account_basics_settings') - .pull-right.span5.settings_specifications - %h4= link_to t('.password'), alter_password_edit_account_path(@account) - %span.pull-left - %a.account_settings_icon.settings_account_password{ href: alter_password_edit_account_path(@account) } - %span= t('.change_passwords') - .account_setting_row - .pull-left.span5.settings_specifications - %h4= link_to t('.api_keys'), account_api_keys_path(@account) - %span.pull-left - %a.account_settings_icon.settings_api_keys{ href: account_api_keys_path(@account) } - %span= t('.api_key_settings') - .pull-right.span5.settings_specifications - %h4= link_to t('.privacy'), edit_account_privacy_account_path - %span.pull-left - %a.account_settings_icon.settings_privacy{ href: edit_account_privacy_account_path } - %span= t('.email_settings') +#account-settings-page + .settings-page-header + .settings-page-header-row + .settings-page-icon + %i.fa.fa-cog + %h2.settings-page-h2= t('.settings') + %p.settings-page-description= t('.configure_desc', name: @account.name) + + .settings-grid + %a.settings_module{ href: edit_account_path(@account) } + .settings-icon-wrapper.gradient-purple + %i.fa.fa-user + .settings-text + %h4= t('.account_basics') + %span.settings_description= t('.account_basics_settings') + + - if my_account?(@account) + %a.settings_module{ href: alter_password_edit_account_path(@account) } + .settings-icon-wrapper.gradient-red + %i.fa.fa-lock + .settings-text + %h4= t('.password') + %span.settings_description= t('.change_passwords') + + %a.settings_module{ href: account_api_keys_path(@account) } + .settings-icon-wrapper.gradient-blue + %i.fa.fa-key + .settings-text + %h4= t('.api_keys') + %span.settings_description= t('.api_key_settings') + + %a.settings_module{ href: edit_account_privacy_account_path } + .settings-icon-wrapper.gradient-green + %i.fa.fa-shield + .settings-text + %h4= t('.privacy') + %span.settings_description= t('.email_settings') diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index e97233382..8e488e8c6 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -11,14 +11,19 @@ %meta{ content: avatar_img_path(@account, 80).to_s, name: 'twitter:image' } %meta{ content: 'https://www.openhub.net', name: 'twitter:domain' } -= render partial: 'accounts/show/summary' -.clearfix -= render partial: 'accounts/show/admin' if current_user_is_admin? -.mezzo.padding_one_top - %h3= t('.dev_history') - .col-sm-12.col-md-12 +#accounts_show_page + = render partial: 'accounts/show/summary' + .clearfix + - if current_user_is_admin? + .admin_panel_page_wrapper + = render partial: 'accounts/show/admin' + .mezzo.padding_one_top - if @account.decorate.account_analysis_status_message %p= @account.decorate.account_analysis_status_message - else - = render partial: 'accounts/show/commits_by_projects' - = render partial: 'accounts/show/commits_by_language' + .dev-history-section + %h2= t('.dev_history') + .dev_chart_section + = render partial: 'accounts/show/commits_by_projects' + .dev_chart_section + = render partial: 'accounts/show/commits_by_language' diff --git a/app/views/accounts/show/_admin.html.haml b/app/views/accounts/show/_admin.html.haml index 22482526b..335bb63b4 100644 --- a/app/views/accounts/show/_admin.html.haml +++ b/app/views/accounts/show/_admin.html.haml @@ -1,41 +1,76 @@ -.well.hidden#admin_actions_opened - %h6= t('.email') - %p= link_to h(@account.email), "mailto:#{@account.email}" - %h6= t('.activity') - %p.ohloh_activity - = render partial: 'accounts/show/action_link', locals: { action: 'edits', path: account_edits_url(@account) } - = render partial: 'accounts/show/posts_link', locals: { posts: @account.posts } - = render partial: 'accounts/show/action_link', - locals: { action: 'reviews', css: '', path: account_reviews_url(@account) } +/ Expanded state (hidden by default, shown when admin opens the panel) +#admin_actions_opened.hidden + .admin_panel_header + .admin_header_left + .admin_lock_badge + %i.icon-lock + %span.admin_panel_title Admin Panel + = link_to 'javascript:void(0);', id: 'close_admin_panel', class: 'admin_chevron_btn', aria: { label: 'Close admin panel' } do + %i.icon-chevron-up{ aria: { hidden: 'true' } } - %h6= t('.actions_title') - %p - %p - - if @account.verifications.empty? - %a.btn.btn-primary.btn-small{ href: manual_verification_account_accesses_path(@account), - data: { method: 'post' }, - title: t('.manual_verification.title', name: @account.name) } - %i.icon-check - = t('.manual_verification.link') - = link_to t('.spammer.link_html'), make_spammer_account_accesses_path(@account), - method: :post, data: { confirm: t('.spammer.confirm', name: @account.name) }, - class: 'btn btn-warning btn-small', title: t('.spammer.title', name: @account.name) -     - = link_to t('.non_spammer.link_html'), make_non_spammer_account_accesses_path(@account), - method: :post, data: { confirm: t('.non_spammer.confirm', name: @account.name) }, - class: 'btn btn-small btn-success', title: t('.non_spammer.title', name: @account.name) -     - - if @account.access.verified? - = link_to t('.delete.link_html'), account_path(@account), - method: :delete, data: { confirm: t('.delete.confirm', name: @account.name) }, - class: 'btn btn-danger btn-small', title: t('.delete.title', name: @account.name) - - else - %span{ title: t('.delete.need_verification') } - %button.btn.btn-default.btn-small{ disabled: 'disabled' } - = t('.delete.link_html') -     - = render partial: 'accounts/show/mark_as_bot' - = link_to t('.panel.close'), 'javascript:void(0);', id: 'close_admin_panel' + .admin_panel_body + .admin_info_section + .admin_info_label + %i.icon-envelope + %span= t('.email') + %p.admin_email_link= link_to h(@account.email), "mailto:#{@account.email}" -.well#admin_actions_closed - = link_to t('.panel.open'), 'javascript:void(0);', id: 'open_admin_panel' + .admin_info_label + %i.icon-bar-chart + %span= t('.activity') + .admin_activity_cards + .admin_activity_card + %i.icon-edit + .admin_activity_card_body + %p.admin_activity_card_title Edits + = render partial: 'accounts/show/action_link', locals: { action: 'edits', path: account_edits_url(@account) } + .admin_activity_card + %i.icon-comment + .admin_activity_card_body + %p.admin_activity_card_title Posts + = render partial: 'accounts/show/posts_link', locals: { posts: @account.posts } + .admin_activity_card + %i.icon-check + .admin_activity_card_body + %p.admin_activity_card_title Reviews + = render partial: 'accounts/show/action_link', locals: { action: 'reviews', css: '', path: account_reviews_url(@account) } + + .admin_actions_section + .admin_info_label.admin_danger_label + %i.icon-warning-sign + %span= t('.actions_title') + .admin_btns_grid + - if @account.verifications.empty? + %a.btn.btn-primary.btn-small{ href: manual_verification_account_accesses_path(@account), + data: { method: 'post' }, + title: t('.manual_verification.title', name: @account.name) } + %i.icon-check + = t('.manual_verification.link') + = link_to t('.spammer.link_html'), make_spammer_account_accesses_path(@account), + method: :post, data: { confirm: t('.spammer.confirm', name: @account.name) }, + class: 'btn btn-warning btn-small', title: t('.spammer.title', name: @account.name) + = link_to t('.non_spammer.link_html'), make_non_spammer_account_accesses_path(@account), + method: :post, data: { confirm: t('.non_spammer.confirm', name: @account.name) }, + class: 'btn btn-small btn-success', title: t('.non_spammer.title', name: @account.name) + - if @account.access.verified? + = link_to t('.delete.link_html'), account_path(@account), + method: :delete, data: { confirm: t('.delete.confirm', name: @account.name) }, + class: 'btn btn-danger btn-small', title: t('.delete.title', name: @account.name) + - else + %span.admin_disabled_btn{ title: t('.delete.need_verification') } + %button.btn.btn-default.btn-small{ disabled: 'disabled' } + = t('.delete.link_html') + = render partial: 'accounts/show/mark_as_bot' + = link_to 'javascript:void(0);', class: 'admin_close_text_btn' do + %i.icon-remove + Close admin panel + +/ Collapsed state (visible by default — amber header bar) +#admin_actions_closed + = link_to 'javascript:void(0);', id: 'open_admin_panel', class: 'admin_panel_header' do + .admin_header_left + .admin_lock_badge + %i.icon-lock + %span.admin_panel_title Admin Panel + .admin_chevron_btn + %i.icon-chevron-down diff --git a/app/views/accounts/show/_commits_by_language.html.haml b/app/views/accounts/show/_commits_by_language.html.haml index c189b03be..17bf7ae78 100644 --- a/app/views/accounts/show/_commits_by_language.html.haml +++ b/app/views/accounts/show/_commits_by_language.html.haml @@ -1,8 +1,10 @@ - chart_url = commits_by_language_account_charts_url(@account, scope: 'regular').to_s +- light_wm = asset_url('charts/watermark_692.png') +- dark_wm = light_wm .margin_top_10 .pull-left#commits_lang %h4= link_to t('.link'), account_languages_path(@account) .pull-left.soft.margin_top_10.margin_left_20= chart_default_time_span .clearfix -.stream_graph.regular#ohloh_streamgraph{ 'datascope' => 'regular', 'datasrc' => chart_url } +.stream_graph.regular#ohloh_streamgraph{ 'datascope' => 'regular', 'datasrc' => chart_url, 'data-light-watermark' => light_wm, 'data-dark-watermark' => dark_wm } diff --git a/app/views/accounts/show/_commits_by_projects.html.haml b/app/views/accounts/show/_commits_by_projects.html.haml index e6249aab6..3edb05ed8 100644 --- a/app/views/accounts/show/_commits_by_projects.html.haml +++ b/app/views/accounts/show/_commits_by_projects.html.haml @@ -5,12 +5,14 @@ .pull-left.soft.margin_top_10.margin_left_20 = chart_default_time_span .clearfix +- light_wm = asset_url('charts/watermark_692.png') +- dark_wm = light_wm - if only_device? %div{ style: 'height:250px;' } - .chart#project_contributions{ style: 'width:450px; height:200px; margin-left:-26px;', 'datasrc' => chart_url } + .chart#project_contributions{ style: 'width:450px; height:200px; margin-left:-26px;', 'datasrc' => chart_url, 'data-light-watermark' => light_wm, 'data-dark-watermark' => dark_wm } - elsif only_tab? %div{ style: 'height:350px;' } - .chart#project_contributions{ style: 'width:750px; height:300px; margin-left:-28px;', 'datasrc' => chart_url } + .chart#project_contributions{ style: 'width:750px; height:300px; margin-left:-28px;', 'datasrc' => chart_url, 'data-light-watermark' => light_wm, 'data-dark-watermark' => dark_wm } - else %div{ style: 'height:350px;' } - .chart#project_contributions{ style: 'width:950px; height:300px; margin-left:-28px;', 'datasrc' => chart_url } + .chart#project_contributions{ style: 'width:950px; height:300px; margin-left:-28px;', 'datasrc' => chart_url, 'data-light-watermark' => light_wm, 'data-dark-watermark' => dark_wm } diff --git a/app/views/accounts/show/_header.html.haml b/app/views/accounts/show/_header.html.haml index fd2792df5..dd64fb7d3 100644 --- a/app/views/accounts/show/_header.html.haml +++ b/app/views/accounts/show/_header.html.haml @@ -1,48 +1,55 @@ -.account_header{ style: 'margin-top: 10px' } - .col-xs-2.col-sm-2.col-md-2#account_icon - = image_tag avatar_img_path(@account, 128), size: '128x128', itemprop: 'image' - - lang = @account.most_experienced_language - - if lang - - lang_style = "background-color: ##{language_color(lang.name)}; color:##{language_text_color(lang.name)};" - .center.primary_language_bg{ style: lang_style } - = lang.nice_name - - else -   - .col-xs-10.col-sm-10.col-md-10#account_header - .pull-left#account_name - %h1{ itemprop: 'name' }= h(@account.name) - .info - = @account.location - - if @account.location.present? - %meta{ itemprop: 'homeLocation', content: @account.location } +.account_header + .account_header_inner + / Avatar + name row + .header_flex_container + #account_icon + = image_tag avatar_img_path(@account, 128), size: '128x128', itemprop: 'image', alt: h(@account.name) + - lang = @account.most_experienced_language + - if lang + - lang_style = "background-color: ##{language_color(lang.name)}; color:##{language_text_color(lang.name)};" + .primary_language_bg{ style: lang_style } + = lang.nice_name + + #account_header + #account_name + %h1{ itemprop: 'name' }= link_to h(@account.name), account_path(@account) + .info_row + - if @account.location.present? + .info + %i.icon-map-marker + = @account.location + %meta{ itemprop: 'homeLocation', content: @account.location } + + - if @account.url.present? + .info + = link_to h(@account.url), target: '_blank', title: @account.url, itemprop: 'url', rel: 'nofollow noopener noreferrer' do + %i.icon-external-link + = h(truncate(@account.url, length: 60)) + + .info#account_affiliation + = render partial: 'accounts/show/header/affiliation', locals: { account: @account } + + / Badges row — full width, horizontal, before action buttons + .mini-badges-section + - badges = @account.badges.map { |badge| BadgeDecorator.new(badge) } + = render partial: 'accounts/badges_row', locals: { badges: badges, header: :large } + + / Action buttons + analyzed timestamp + .header_actions + .header_buttons - if my_account?(@account) || current_user_is_admin? - - if @account.location.present? - %span.seperator  |  - != link_to bootstrap_icon('icon-cogs', t('.settings')), settings_account_path(@account) + = link_to bootstrap_icon('icon-cogs', t('.settings')), settings_account_path(@account), class: 'action_button' - if current_user_is_admin? - | - = link_to bootstrap_icon('icon-legal', t('.view_job')), admin_account_account_analysis_jobs_path(@account) - - if @account.url.present? - .info - = link_to h(@account.url), target: '_blank', title: @account.url, itemprop: 'url', rel: 'nofollow' do - %i.icon-external-link= "  #{h(truncate(@account.url, length: 60))}".html_safe - .info#account_affiliation - = render partial: 'accounts/show/header/affiliation', locals: { account: @account } - #kudo_section - %meta{ itemprop: 'interactionCount', content: @account.kudo_rank } - .social-connect - - if my_account?(@account) - %i.icon-twitter{ style: 'color: #ccc' } - - elsif @account.twitter_account.present? - = link_to h(@twitter_detail.url(request.url)), class: 'link_no_underline', - rel: 'popup', title: "Follow @#{@account.twitter_account}" do - %i.icon-twitter - = render partial: 'accounts/show/header/kudo_button', locals: { css: 'btn-mini btn-info' } - .mini-badges-section.pull-right - - badges = @account.badges.map { |badge| BadgeDecorator.new(badge) } - = render partial: 'badges_row', locals: { badges: badges, header: :large } + = link_to bootstrap_icon('icon-legal', t('.view_job')), admin_account_account_analysis_jobs_path(@account), class: 'action_button', target: '_blank', rel: 'noopener noreferrer' + .kudo_button_wrapper + = render partial: 'accounts/show/header/kudo_button', locals: { css: 'kudo_action_button' } + - if !my_account?(@account) && @account.twitter_account.present? + - twitter_detail = @twitter_detail || TwitterDetail.new(@account) + = link_to h(twitter_detail.url(request.url)), class: 'link_no_underline twitter_link_header', + rel: 'popup', title: "Follow @#{@account.twitter_account}" do + %i.icon-twitter + + / Analyzed timestamp — below buttons row + = render partial: 'accounts/show/account_analysis_timestamp', locals: { best_account_analysis: @account.best_account_analysis } -.clearfix   -.col-md-12 - .mezzo = render partial: 'layouts/partials/alert' diff --git a/app/views/accounts/show/_summary.html.haml b/app/views/accounts/show/_summary.html.haml index 0f62d089a..7f173443f 100644 --- a/app/views/accounts/show/_summary.html.haml +++ b/app/views/accounts/show/_summary.html.haml @@ -1,13 +1,18 @@ #accounts_show - %h2.pull-left= t('.title') - = render partial: 'accounts/show/account_analysis_timestamp', locals: { best_account_analysis: @account.best_account_analysis } - .clearfix - .col-sm-5.col-md-5 - .margin_bottom_10px{ itemprop: 'description' } - = render partial: 'accounts/show/description', locals: { markup: @account.markup, desc_size: ACCOUNT_DESC_LENGTH } - %h4.projects_used - = link_to t('.projects_used'), account_stacks_path(@account) - .stacked_projects - = render partial: 'accounts/show/stacked_projects' - .col-sm-7.baseball_card - = render partial: 'accounts/show/baseball_card' + %h2= t('.title') + + / Card wrapper with grid layout (stacks on mobile, 2-col on md+) + .summary_card_wrapper + .summary_grid + / Left column: Projects Used + .summary_column_left + .margin_bottom_10px{ itemprop: 'description' } + = render partial: 'accounts/show/description', locals: { markup: @account.markup, desc_size: ACCOUNT_DESC_LENGTH } + %h4.projects_used + = link_to t('.projects_used'), account_stacks_path(@account) + .stacked_projects + = render partial: 'accounts/show/stacked_projects' + + / Right column: Baseball Card stats + .summary_column_right.baseball_card + = render partial: 'accounts/show/baseball_card' diff --git a/app/views/accounts/show/header/_kudo_button.html.haml b/app/views/accounts/show/header/_kudo_button.html.haml index 25d7d77d9..84ff4c502 100644 --- a/app/views/accounts/show/header/_kudo_button.html.haml +++ b/app/views/accounts/show/header/_kudo_button.html.haml @@ -7,8 +7,13 @@ - if kudo = form_for kudo, action: account_kudo_path(current_user, kudo), method: :delete do = button_tag class: "btn kudo-btn #{css}" do - %i.icon.icon-undo - #{t('undo')} Kudo + %svg.kudo-btn-icon{ xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' } + %path{ d: 'M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3z' } + %path{ d: 'M17 2h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17' } + = "#{t('undo')} Kudo" - else - = link_to "#{t('give')} Kudo", 'javascript:', class: "btn kudo-btn #{css}", - onclick: "tb_show('#{t('give')} Kudos #{t('to')} #{name}', '#{action}', false); return false;" + = link_to 'javascript:', class: "btn kudo-btn #{css}", onclick: "tb_show('#{t('give')} Kudos #{t('to')} #{name}', '#{action}', false); return false;" do + %svg.kudo-btn-icon{ xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' } + %path{ d: 'M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3z' } + %path{ d: 'M7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3' } + = "#{t('give')} Kudo" diff --git a/app/views/activation_resends/_fields.html.haml b/app/views/activation_resends/_fields.html.haml index ad8d899c8..fbb74160b 100644 --- a/app/views/activation_resends/_fields.html.haml +++ b/app/views/activation_resends/_fields.html.haml @@ -1,13 +1,15 @@ -.col-md-10 - .well - = form_tag action: :create do - %fieldset - .control-group - = label_tag t('email_address'), nil, class: 'control-label required' - .controls - = text_field_tag :email, current_user.try(:email) - .error= @errors - %p.help-block= t('.enter_email') += form_tag action: :create do + %fieldset + .signup-field-group + %label.signup-field-label + = t('email_address') + %span.pwd-required * + .signup-field-input-wrapper + %i.fa.fa-envelope-o.signup-field-icon + = text_field_tag :email, current_user.try(:email), class: 'signup-field-input', placeholder: 'you@example.com' + - if @errors + .signup-field-group + %span.error= @errors - .actions - = submit_tag t('.submit'), class: 'btn btn-primary' + .signup-field-actions + = submit_tag t('.submit'), class: 'signup-submit-btn' diff --git a/app/views/activation_resends/new.html.haml b/app/views/activation_resends/new.html.haml index 3254e0e00..fbdadc3c5 100644 --- a/app/views/activation_resends/new.html.haml +++ b/app/views/activation_resends/new.html.haml @@ -1,5 +1,28 @@ - content_for(:html_title) { 'Activation Resend' } +- page_context[:suppress_flash] = true -%h3= t('.heading') +.signup-page + / ── Left Panel ────────────────────────────────────────── + = render 'layouts/partials/auth_left_panel', + hero_lines: [t('activation_resends.new.hero_title')], + hero_accent: t('activation_resends.new.hero_accent'), + tagline: t('activation_resends.new.tagline') -= render 'fields' + / ── Right Panel ───────────────────────────────────────── + .signup-right-panel + = render partial: 'layouts/partials/alert' + .signup-form-card + .signup-form-header + %h2.signup-form-title Activation Resend + %p.signup-form-subtitle= t('activation_resends.fields.enter_email') + + = render 'fields' + + .pwd-reset-back + = link_to new_session_path, class: 'signin-helper-link signin-helper-link--primary' do + %i.fa.fa-arrow-left + Back to Sign In + + .signup-terms-note + Need help? + = link_to 'Contact Support', 'mailto:info@openhub.net', class: 'signup-terms-link' diff --git a/app/views/aliases/_about_aliases.html.haml b/app/views/aliases/_about_aliases.html.haml index a41f92ff1..ba074d1ef 100644 --- a/app/views/aliases/_about_aliases.html.haml +++ b/app/views/aliases/_about_aliases.html.haml @@ -1,11 +1,18 @@ -%h4.nomargin - %span.soft= t('.about') - = t('.aliases') -.col-md-6.margin_left_30 - %ul - %li - = t('.about_desc_1') - %li - = t('.about_desc_2') - %li - = t('.about_desc_3') +.about-project-basics-card + .card-header + %h4.card-title + %span.soft= t('.about') + = t('.aliases') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + + .card-content + .about-content-wrapper + .col-md-6.margin_left_30 + %ul + %li + = t('.about_desc_1') + %li + = t('.about_desc_2') + %li + = t('.about_desc_3') diff --git a/app/views/aliases/_fields.html.haml b/app/views/aliases/_fields.html.haml index be66d29cd..3082e73c4 100644 --- a/app/views/aliases/_fields.html.haml +++ b/app/views/aliases/_fields.html.haml @@ -1,21 +1,19 @@ -.well.col-md-10.col-md-offset-1 - .wrapper - .pull-left.margin_right_5.margin_top_5 - = t('aliases.contributions_by') - .pull-left.margin_right_5.chosen#value_select +.alias-fields + .alias-field-group + %label.alias-label{ for: 'commit_name_id' }= t('aliases.contributions_by') + .alias-select-wrap.chosen#value_select = select_tag :commit_name_id, options_for_select(@committer_names.map { |n| [n.name, n.id] }), class: 'chzn-select value-select', url: preferred_names_project_aliases_path(@project) - .pull-left.margin_right_5.margin_top_5 - %span= t('aliases.attributed_to') - .pull-left.margin_right_5 + .alias-field-group + %label.alias-label{ for: 'preferred_name_id' }= t('aliases.attributed_to') + .alias-select-wrap #preferred_name= render partial: 'preferred_names' - .pull-left.margin_right_5 - %span.spinner= image_tag 'spinner.gif' - .clearfix - .act.margin_top_5 + %span.spinner= image_tag 'spinner.gif', alt: '' + + .alias-actions - if logged_in? && @project.edit_authorized? - = submit_tag t('aliases.save_alias'), class: 'submit btn btn-primary btn-small', id: 'submit_button' + = submit_tag t('aliases.save_alias'), class: 'btn btn-primary alias-submit', id: 'submit_button' - else - = disabled_button t('aliases.save_alias'), class: 'btn-primary' + = disabled_button t('aliases.save_alias'), class: 'btn-primary alias-submit' diff --git a/app/views/aliases/index.html.haml b/app/views/aliases/index.html.haml index daa4359fe..99b0385cf 100644 --- a/app/views/aliases/index.html.haml +++ b/app/views/aliases/index.html.haml @@ -8,7 +8,6 @@ = link_to t('settings'), settings_project_path(@project)  :  = t('aliases.aliases') - = project_analysis_timestamp(@project) .clearfix .alias - if @aliases.length + @best_analysis_aliases.length > 0 diff --git a/app/views/aliases/new.html.haml b/app/views/aliases/new.html.haml index 18c71a839..e91e60397 100644 --- a/app/views/aliases/new.html.haml +++ b/app/views/aliases/new.html.haml @@ -4,20 +4,25 @@ @alias ||= Alias.new @committer_names ||= Alias.committer_names(@project) -%h2.pull-left - = link_to t('settings'), settings_project_path(@project) -  :  - = link_to t('aliases.aliases'), project_aliases_path(@project) -  : #{t('aliases.new_alias')} -= project_analysis_timestamp(@project) +#alias-new-page + .alias-page-header + %h2.alias-page-title + = link_to t('settings'), settings_project_path(@project) + %span.alias-page-title__sep › + = link_to t('aliases.aliases'), project_aliases_path(@project) + %span.alias-page-title__sep › + = t('aliases.new_alias') -.clearfix -.alias - - if @committer_names.length.zero? - .alert.alert-info.alert-block= t('.no_more_alias') - - else - = form_for [@project, @alias] do - = render partial: 'fields' + .row + .col-md-8.col-sm-10.col-xs-12.center-block + - if @committer_names.length.zero? + .alert.alert-info.alert-block.modern-alert + .alert-accent-strip + .alert-inner + = t('.no_more_alias') + - else + .oh-card + = form_for [@project, @alias] do + = render partial: 'fields' -.clearfix -= render partial: 'about_aliases' + = render partial: 'about_aliases' diff --git a/app/views/alter_passwords/edit.html.haml b/app/views/alter_passwords/edit.html.haml index 3a14f78d0..d75a953d3 100644 --- a/app/views/alter_passwords/edit.html.haml +++ b/app/views/alter_passwords/edit.html.haml @@ -1,33 +1,45 @@ - content_for(:html_title) do = t('.edit_password_title', account: @account.name) -%h2= t('.settings_password_html', href: link_to(t('.settings'), settings_account_path(@account))) -.well.col-md-5#center_position +%h2.alter-password-heading + = link_to t('.settings'), settings_account_path(@account) + = ' : ' + %span.account-basics-title= t('.password') +.oh-card.col-md-7#alter-password-form = form_for(@account, url: alter_password_edit_account_path) do |f| %fieldset %legend.margin_left_30 = t('.change_your_password') .control-group.center_form_position - %label.required.strong= t('.current_password') + = f.label :current_password, t('.current_password'), class: 'required strong' .controls - = f.password_field :current_password, class: 'input-xlarge' + = f.password_field :current_password, class: 'form-control' - password_error_tag @account, :current_password %p.help-block = t('.your_current_password') .control-group.center_form_position - %label.required.strong= t('.new_password') + = f.label :password, t('.new_password'), class: 'required strong' .controls - = f.password_field :password, class: 'input-xlarge' + = f.password_field :password, class: 'form-control' - password_error_tag @account, :password %p.help-block = t('.your_new_password') .actions.margin_left_30 = submit_tag(t('.update_password'), class: 'btn btn-primary btn-small') -.col-md-6 - %h4 - .soft.inline= t('.about') - = t('.password') - %ul.margin_left_40{ style: 'margin-top: -20px' } - %li= t('.openhub_foss_resume') - %li= t('.password_specifications') - %li= t('.confirmation_email') +.about-account-basics-card.alter-password-about + .card-header + %h4.card-title + %span.soft= t('.about') + = t('.password') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + + .card-content + .about-content-wrapper + .col-md-6 + %ul + %li= t('.openhub_foss_resume') + %li= t('.password_specifications') + .col-md-6 + %ul + %li= t('.confirmation_email') diff --git a/app/views/alter_passwords/new.html.haml b/app/views/alter_passwords/new.html.haml index 50e50c4e7..6763dbcc0 100644 --- a/app/views/alter_passwords/new.html.haml +++ b/app/views/alter_passwords/new.html.haml @@ -8,7 +8,7 @@ .row .col-md-7 - .well + .oh-card = form_for :password, url: passwords_path do |f| %fieldset .form-group diff --git a/app/views/analyses/_analysis_graphs.html.haml b/app/views/analyses/_analysis_graphs.html.haml index adc5e9f97..ebc247a11 100644 --- a/app/views/analyses/_analysis_graphs.html.haml +++ b/app/views/analyses/_analysis_graphs.html.haml @@ -5,16 +5,17 @@ show_chart, reason = analysis.decorate.display_chart? - if show_chart + - watermark_914 = asset_url('charts/watermark_914.png') .row .col-md-12.no_margin_left %h4= t('.code_comments_blanks') - .chart.watermark914#code_analysis_chart{ 'datasrc' => code_chart, style: 'height: 340px' } + .chart.watermark914#code_analysis_chart{ 'datasrc' => code_chart, style: 'height: 340px', 'data-light-watermark' => watermark_914, 'data-dark-watermark' => watermark_914 } .row .col-md-12 %br %br %h4= t('.loc') - .chart.watermark914#language_analysis_chart{ 'datasrc' => language_chart, style: 'height: 340px' } + .chart.watermark914#language_analysis_chart{ 'datasrc' => language_chart, style: 'height: 340px', 'data-light-watermark' => watermark_914, 'data-dark-watermark' => watermark_914 } - else .row .code_analysis_error diff --git a/app/views/analyses/_language_table.html.haml b/app/views/analyses/_language_table.html.haml index 94e753abd..57da5ff81 100644 --- a/app/views/analyses/_language_table.html.haml +++ b/app/views/analyses/_language_table.html.haml @@ -1,46 +1,45 @@ %h4 = t('.title') -%table.table.table-striped.table-condensed#analyses_language_table - %thead - %tr - %tr - %th{ width: '3%' } - %th{ width: '12%', scope: 'col' }= t('.language') - %th.center{ width: '12%', scope: 'col' }= t('.codes') - %th.center{ width: '14%', scope: 'col' }= t('.comments') - %th.center{ width: '14%', scope: 'col' }= t('.comment_ratio') - %th.center{ width: '11%', scope: 'col' }= t('.blanks') - %th.center{ width: '11%', scope: 'col' }= t('.total_lines') - %th.left{ width: '20%', scope: 'col' }= t('.total_percent') - - - languages_breakdown.each do |lb| - %tr{ class: cycle('odd', 'even') } - %td{ style: "background-color: ##{language_color(lb.language_name)}" }   - %td - %span{ itemscope: '', itemtype: 'http://schema.org/Language' } - %span{ itemprop: 'name' }= link_to lb.language_nice_name, language_path(lb.language_name) - %td.center= number_with_delimiter(lb.code_total) - %td.center= number_with_delimiter(lb.comments_total) - %td.center= comments_ratio_from_lanaguage_breakdown(lb) - %td.center= number_with_delimiter(lb.blanks_total) - %td.center= number_with_delimiter(lb.code_total + lb.comments_total + lb.blanks_total) +.table-responsive + %table.table.table-striped.table-condensed#analyses_language_table + %thead + %tr + %th{ width: '3%', scope: 'col', aria: { label: t('.color') } } + %th{ width: '12%', scope: 'col' }= t('.language') + %th.center{ width: '12%', scope: 'col' }= t('.codes') + %th.center{ width: '14%', scope: 'col' }= t('.comments') + %th.center{ width: '14%', scope: 'col' }= t('.comment_ratio') + %th.center{ width: '11%', scope: 'col' }= t('.blanks') + %th.center{ width: '11%', scope: 'col' }= t('.total_lines') + %th.left{ width: '20%', scope: 'col' }= t('.total_percent') + %tbody + - languages_breakdown.each do |lb| + %tr{ class: cycle('odd', 'even') } + %td{ style: "background-color: ##{language_color(lb.language_name)}" }   + %td + %span{ itemscope: '', itemtype: 'http://schema.org/Language' } + %span{ itemprop: 'name' }= link_to lb.language_nice_name, language_path(lb.language_name) + %td.center= number_with_delimiter(lb.code_total) + %td.center= number_with_delimiter(lb.comments_total) + %td.center= comments_ratio_from_lanaguage_breakdown(lb) + %td.center= number_with_delimiter(lb.blanks_total) + %td.center= number_with_delimiter(lb.code_total + lb.comments_total + lb.blanks_total) + %td + .bar.pull-left + .barfill{ style: barfill_css(languages_breakdown, lb) } + %span{ itemscope: '', itemprop: 'aggregateRating', itemtype: 'http://schema.org/AggregateRating' } + %span.pull-right{ itemprop: 'ratingValue', style: 'margin-left:5px;' } + = total_percent(languages_breakdown, lb) + .clearfix + %tr %td - .bar.pull-left - .barfill{ style: barfill_css(languages_breakdown, lb) } - %span{ itemscope: '', itemprop: 'aggregateRating', itemtype: 'http://schema.org/AggregateRating' } - %span.pull-right{ itemprop: 'ratingValue', style: 'margin-left:5px;' } - = total_percent(languages_breakdown, lb) - .clearfix - - %tr - %td - %td.center= t('.total') - %td.center= number_with_delimiter analysis_total_detail(languages_breakdown, 'code_total') - %td.center= number_with_delimiter analysis_total_detail(languages_breakdown, 'comments_total') - %td.center - %td.center= number_with_delimiter analysis_total_detail(languages_breakdown, 'blanks_total') - %td.center= number_with_delimiter analysis_total_lines(languages_breakdown) - %td.center - %tr - %td{ colspan: 8 } + %td.center= t('.total') + %td.center= number_with_delimiter analysis_total_detail(languages_breakdown, 'code_total') + %td.center= number_with_delimiter analysis_total_detail(languages_breakdown, 'comments_total') + %td.center + %td.center= number_with_delimiter analysis_total_detail(languages_breakdown, 'blanks_total') + %td.center= number_with_delimiter analysis_total_lines(languages_breakdown) + %td.center + %tr + %td{ colspan: 8 } diff --git a/app/views/analyses/_projects_languages_details.html.haml b/app/views/analyses/_projects_languages_details.html.haml index 349eeef42..63f04b2d5 100644 --- a/app/views/analyses/_projects_languages_details.html.haml +++ b/app/views/analyses/_projects_languages_details.html.haml @@ -4,7 +4,7 @@ comment_lines = analysis_total_detail(languages_breakdown, 'comments_total') blank_lines = analysis_total_detail(languages_breakdown, 'blanks_total') -.well{ style: 'border: none; box-shadow: none; border-radius: 0' } +.oh-card{ style: 'border: none; box-shadow: none; border-radius: 0' } %table %tr %td{ width: '20%', scope: 'col' }= t('.total_lines') diff --git a/app/views/analyses/languages_summary.html.haml b/app/views/analyses/languages_summary.html.haml index 99211f1ef..3cbc52e9d 100644 --- a/app/views/analyses/languages_summary.html.haml +++ b/app/views/analyses/languages_summary.html.haml @@ -5,7 +5,6 @@ .project_content_title %h2.pull-left= t('.title') - = project_analysis_timestamp(@project) - if @languages_breakdown.any? %br diff --git a/app/views/api/vulnerabilities/show.html.haml b/app/views/api/vulnerabilities/show.html.haml index 9ba93ad86..f837da5e6 100644 --- a/app/views/api/vulnerabilities/show.html.haml +++ b/app/views/api/vulnerabilities/show.html.haml @@ -16,9 +16,9 @@ %span.meta-label Last Modified: %span.meta-value= humanize_datetime(@response['lastModifiedDate']) .col-lg-2.col-xs-3.cybersecurity-logo.pull-right - %a{ href: "https://www.blackduck.com/resources/cybersecurity-research-center.html", target: '_blank' } + %a{ href: "https://www.blackduck.com/resources/cybersecurity-research-center.html", target: '_blank', rel: 'noopener noreferrer' } %img{ itemprop: 'image', src: image_path('logo/cyrc-500.png'), alt: 'Black Duck Cybersecurity' } - = link_to 'Learn more about BDSAs', 'https://www.blackduck.com/blog/black-duck-security-advisories-benefits.html', class: 'btn btn-primary learn-more btn-sm', target: '_blank' + = link_to 'Learn more about BDSAs', 'https://www.blackduck.com/blog/black-duck-security-advisories-benefits.html', class: 'btn btn-primary learn-more btn-sm', target: '_blank', rel: 'noopener noreferrer' .row.details .col-lg-6.col-xs-12 .summary-info diff --git a/app/views/api_keys/_admin_api_key.html.haml b/app/views/api_keys/_admin_api_key.html.haml index bc3e73096..6a7fc5cb3 100644 --- a/app/views/api_keys/_admin_api_key.html.haml +++ b/app/views/api_keys/_admin_api_key.html.haml @@ -1,6 +1,6 @@ .row .col-md-10 - .well + .oh-card %table %tbody %tr diff --git a/app/views/api_keys/_api_key.html.haml b/app/views/api_keys/_api_key.html.haml index a07d73c4d..daf1d7bd6 100644 --- a/app/views/api_keys/_api_key.html.haml +++ b/app/views/api_keys/_api_key.html.haml @@ -1,5 +1,5 @@ - oauth_application = api_key.oauth_application || Doorkeeper::Application.new -.well.col-md-12 +.oh-card.col-md-12 .padding_20 %h5.pull-right.timestamp %i.icon-time   diff --git a/app/views/api_keys/_form.html.haml b/app/views/api_keys/_form.html.haml index 9e9624040..a61140295 100644 --- a/app/views/api_keys/_form.html.haml +++ b/app/views/api_keys/_form.html.haml @@ -7,47 +7,52 @@ settings_link = link_to(t(:settings), settings_account_path(@account)) account_api_keys_link = link_to(t(:api_keys_title), account_api_keys_url(@account)) -%h2! #{settings_link} : #{account_api_keys_link} : #{@api_key.new_record? ? t(:new) : t(:edit)} +%h2.alter-password-heading.margin_left_15 + != settings_link + = ' : ' + != account_api_keys_link + = ' : ' + %span.account-basics-title= @api_key.new_record? ? t(:new) : t(:edit) = form_for [@account, @api_key] do |f| - .col-md-11.center-block.well.margin_left_20.padding_right_0 - %fieldset + #api-key-form.col-md-11.center-block.oh-card.margin_left_20.padding_right_0 + %fieldset.margin_left_15 - @api_key.oauth_application || @api_key.build_oauth_application = f.fields_for :oauth_application do |ff| .control-group - %label.control-label.required= t 'api_keys.form.application_name' + = ff.label :name, t('api_keys.form.application_name'), class: 'control-label required' .controls - = ff.text_field :name, max_length: 50, class: 'col-md-7' + = ff.text_field :name, max_length: 50, class: 'form-control' - error_tag @api_key.oauth_application, :name .clearfix   .control-group - %label.control-label.required= t 'api_keys.redirect_uri' + = ff.label :redirect_uri, t('api_keys.redirect_uri'), class: 'control-label required' .controls - = ff.text_field :redirect_uri, max_length: 50, class: 'col-md-7' + = ff.text_field :redirect_uri, max_length: 50, class: 'form-control' - error_tag @api_key.oauth_application, :redirect_uri .clearfix   .control-group - %label.control-label= t 'api_keys.form.home_page_url' + = f.label :url, t('api_keys.form.home_page_url'), class: 'control-label' .controls - = f.text_field :url, max_length: 50, class: 'col-md-7' + = f.text_field :url, max_length: 50, class: 'form-control' - error_tag @api_key, :url %p.help-block= t 'api_keys.form.home_page_url_info' .control-group - %label.control-label.required= t 'api_keys.form.description' + = f.label :description, t('api_keys.form.description'), class: 'control-label required' .controls - = f.text_area :description, rows: 10, max_length: 2000, class: 'col-md-7' + = f.text_area :description, rows: 10, max_length: 2000, class: 'form-control' - error_tag @api_key, :description .clearfix   %p.help-block= t 'api_keys.form.description_info' - if current_user_is_admin? .control-group - %label.control-label= t 'api_keys.form.daily_request_limit' + = f.label :daily_limit, t('api_keys.form.daily_request_limit'), class: 'control-label' .controls - = f.text_field :daily_limit, max_length: 10, class: 'col-md-4' + = f.text_field :daily_limit, max_length: 10, class: 'form-control' - error_tag @api_key, :daily_limit .clearfix %p.help-block= t 'api_keys.form.daily_request_limit_admin' @@ -61,28 +66,33 @@ %p.help-block= t 'api_keys.form.daily_request_limit_user', default_daily_limit: default_daily_limit - if @api_key.new_record? .control-group - %label.checkbox.no_padding#request_limit + %label.checkbox#request_limit = f.check_box :terms != t 'api_keys.form.agree_to_terms', terms_link: terms_link, terms_2_link: terms_2_link - error_tag @api_key, :terms .actions - %input.btn.btn-primary.btn-small{ type: 'submit', value: t(:save_changes) } + %input.btn.btn-primary{ type: 'submit', value: t(:save_changes) } - unless @api_key.new_record? - %a.btn.btn-small.btn-danger{ href: account_api_key_path(@account, @api_key), - data: { method: :delete, confirm: t('api_keys.form.delete_confirm') }, - style: 'padding: 6px 6px 5px 6px;' } - %i.icon-trash= t 'api_keys.form.delete' + %a.btn.btn-danger.btn-lg{ href: account_api_key_path(@account, @api_key), + data: { method: :delete, confirm: t('api_keys.form.delete_confirm') } } + %i.icon-white.icon-trash{ 'aria-hidden' => 'true' } + = t 'api_keys.form.delete' -.clearfix -%h4.margin_bottom_0 - %span.soft= t :about - = t :api_keys_title -.col-md-5.margin_left_30 - %ul - %li!= t('api_keys.help_1', terms_link: terms_link, terms_2_link: terms_2_link) - %li= t('api_keys.help_4') - %li= t('api_keys.help_5') -.col-md-5.margin_left_20 - %ul - %li= t('api_keys.help_6') - %li!= t('api_keys.help_7', contacts_link: contacts_link) +.about-account-basics-card.alter-password-about.margin_left_15 + .card-header + %h4.card-title + %span.soft= t(:about) + = t(:api_keys_title) + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + .card-content + .about-content-wrapper + .col-md-6 + %ul + %li!= t('api_keys.help_1', terms_link: terms_link, terms_2_link: terms_2_link) + %li= t('api_keys.help_4') + %li= t('api_keys.help_5') + .col-md-6 + %ul + %li= t('api_keys.help_6') + %li!= t('api_keys.help_7', contacts_link: contacts_link) diff --git a/app/views/api_keys/admin_index.html.haml b/app/views/api_keys/admin_index.html.haml index 788ec7d08..d646107fa 100644 --- a/app/views/api_keys/admin_index.html.haml +++ b/app/views/api_keys/admin_index.html.haml @@ -10,7 +10,7 @@ .api_keys - if !@api_keys.empty? = render partial: 'admin_api_key', collection: @api_keys, as: 'api_key' - = will_paginate @api_keys + = render 'shared/modern_pagination', collection: @api_keys - elsif !params[:query].blank? .alert.alert-info %a.close{ href: '#', 'data-dismiss' => 'alert' } x diff --git a/app/views/api_keys/index.html.haml b/app/views/api_keys/index.html.haml index bcd90d952..4a551493d 100644 --- a/app/views/api_keys/index.html.haml +++ b/app/views/api_keys/index.html.haml @@ -4,25 +4,34 @@ %h2.float_left = link_to(t(:settings), settings_account_path(@account))  :  - = t('api_keys_title') + %span.account-basics-title= t('api_keys_title') .clearfix .api_keys - unless @api_keys.empty? = render partial: 'api_key', collection: @api_keys - = will_paginate @api_keys + = render 'shared/modern_pagination', collection: @api_keys - if @api_keys.size < ApiKey::KEY_LIMIT_PER_ACCOUNT %a.btn.btn-primary.btn-small.margin_top_10{ href: new_account_api_key_path(@account) }= t('api_keys.request_new') -%h4.margin_top_20.margin_bottom_0 - %span.soft= t(:about) - = t('api_keys_title') -.col-md-6 - %ul.padding_left_30 - - terms_link = blog_link_to(link: :terms, link_text: t('api_keys.terms_link')) - - terms_2_link = blog_link_to(link: :additional_terms, link_text: t('api_keys.terms_2_link')) - - url = 'https://github.com/blackducksoftware/ohloh_api#ohloh-api-documentation' - - getting_started_link = link_to(t('api_keys.getting_started_link'), url, target:'_blank') - %li!= t('api_keys.help_1', terms_link: terms_link, terms_2_link: terms_2_link) - %li!= t('api_keys.help_2') - %li!= t('api_keys.help_3', getting_started_link: getting_started_link) +.about-account-basics-card.alter-password-about + .card-header + %h4.card-title + %span.soft= t(:about) + = t('api_keys_title') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + + .card-content + .about-content-wrapper + - terms_link = blog_link_to(link: :terms, link_text: t('api_keys.terms_link')) + - terms_2_link = blog_link_to(link: :additional_terms, link_text: t('api_keys.terms_2_link')) + - url = 'https://github.com/blackducksoftware/ohloh_api#ohloh-api-documentation' + - getting_started_link = link_to(t('api_keys.getting_started_link'), url, target:'_blank') + .col-md-6 + %ul + %li!= t('api_keys.help_1', terms_link: terms_link, terms_2_link: terms_2_link) + %li!= t('api_keys.help_2') + .col-md-6 + %ul + %li!= t('api_keys.help_3', getting_started_link: getting_started_link) diff --git a/app/views/authentications/_fields.html.haml b/app/views/authentications/_fields.html.haml index ca3ce88db..f91ea6cf0 100644 --- a/app/views/authentications/_fields.html.haml +++ b/app/views/authentications/_fields.html.haml @@ -1,5 +1,5 @@ .row-fluid - .well.well-large.col-md-8.col-md-offset-2.margin_top_20 + .oh-card.col-md-8.col-md-offset-2.margin_top_20 .verification-buttons-container = link_to 'javascript:', { class: 'btn btn-primary github-oauth' }.merge(github_data_attributes) do %i.fa.fa-github diff --git a/app/views/commits/_commit.html.haml b/app/views/commits/_commit.html.haml index 0e3ffb18d..e454aff78 100644 --- a/app/views/commits/_commit.html.haml +++ b/app/views/commits/_commit.html.haml @@ -1,36 +1,35 @@ -%table.table.table-striped.table-condensed{ style: 'width:98%;' } - %thead - %tr{ id: 'dingus-row' } - %td{ colspan: '7' } - - if search_dingus - = render 'shared/search_dingus/commits_or_contributor_search', items: @commits, type: :commits - %tr - %th{ width: '40%' }= t('.commit_message') - %th{ width: '17%' }= t('.contributor') - %th.center{ width: '5%' }= t('.files_modified') - %th.center{ width: '5%' }= t('.lines_added') - %th.center{ width: '5%' }= t('.lines_removed') - %th.center{ width: '18%' }= t('.code_location') - %th{ width: '10%' }= t('.date') - %tbody - - @commits.each do |commit| - - commit_contributor = @commit_contributor || @commit_contributors.fetch(commit.name_id, []).first - - next if commit_contributor.nil? - %tr{ class: highlight(commit.time) } - %td= link_to h(commit.obfuscate_email(commit.comment).split("\n").first), project_commit_path(@project, commit) - %td - = avatar_for(commit_contributor.person) - .committer_name{ style: 'padding-top:5px;' } - = render partial: 'committer_name', locals: { commit_contributor: commit_contributor } - %td{ colspan: '4', class: "commit_#{commit.to_param}" } - %span.icon-play-circle{ id: "icon_play_circle_#{commit.to_param}" } - = link_to t('.more'), '#', class: 'commit-details', - commit_id: commit.to_param, project_id: @project.to_param - %span.spinner.hidden{ id: "spinner_#{commit.to_param}" } - = image_tag 'spinner.gif' - %td - %abbr.date{ title: commit.time.to_s } - = t('time_ago', time: time_ago_in_words(commit.time)) -= will_paginate @commits if @commits.respond_to?(:total_pages) +- if search_dingus + = render 'shared/search_dingus/commits_or_contributor_search', items: @commits, type: :commits +.table-responsive + %table.table.table-striped.table-condensed + %thead + %tr + %th{ width: '40%' }= t('.commit_message') + %th{ width: '17%' }= t('.contributor') + %th.center{ width: '5%' }= t('.files_modified') + %th.center{ width: '5%' }= t('.lines_added') + %th.center{ width: '5%' }= t('.lines_removed') + %th.center{ width: '18%' }= t('.code_location') + %th{ width: '10%' }= t('.date') + %tbody + - @commits.each do |commit| + - commit_contributor = @commit_contributor || @commit_contributors.fetch(commit.name_id, []).first + - next if commit_contributor.nil? + %tr{ class: highlight(commit.time) } + %td= link_to h(commit.obfuscate_email(commit.comment).split("\n").first), project_commit_path(@project, commit) + %td + = avatar_for(commit_contributor.person) + .committer_name{ style: 'padding-top:5px;' } + = render partial: 'committer_name', locals: { commit_contributor: commit_contributor } + %td{ colspan: '4', class: "commit_#{commit.to_param}" } + %span.icon-play-circle{ id: "icon_play_circle_#{commit.to_param}" } + = link_to t('.more'), '#', class: 'commit-details', + commit_id: commit.to_param, project_id: @project.to_param + %span.spinner.hidden{ id: "spinner_#{commit.to_param}" } + = image_tag 'spinner.gif', alt: '' + %td + %abbr.date{ title: commit.time.to_s } + = t('time_ago', time: time_ago_in_words(commit.time)) += render 'shared/modern_pagination', collection: @commits = render partial: 'shared/alert', locals: { message: t('.no_data') } if @commits.blank? diff --git a/app/views/commits/index.html.haml b/app/views/commits/index.html.haml index f4abb7367..65ae78f74 100644 --- a/app/views/commits/index.html.haml +++ b/app/views/commits/index.html.haml @@ -7,7 +7,6 @@ %h2.pull-left = link_to t('.commits'), summary_project_commits_path(@project) = t('.listings') - = project_analysis_timestamp(@project) .col-md-12 - if analysis.empty? diff --git a/app/views/commits/show.html.haml b/app/views/commits/show.html.haml index 146e7b3d2..fa9806bfd 100644 --- a/app/views/commits/show.html.haml +++ b/app/views/commits/show.html.haml @@ -11,7 +11,7 @@ = project_analysis_timestamp(@project) .col-md-10.col-md-offset-1 - .well + .oh-card %p.commit_id = t('.commit_id') = @commit.nice_id @@ -110,7 +110,7 @@ - else = render 'commits/code_added_removed', summary: summary, klass: '' -= will_paginate @diffs += render 'shared/modern_pagination', collection: @diffs .clearfix - if @diffs.blank? diff --git a/app/views/commits/summary.html.haml b/app/views/commits/summary.html.haml index 71a94802a..5349d14f0 100644 --- a/app/views/commits/summary.html.haml +++ b/app/views/commits/summary.html.haml @@ -3,7 +3,6 @@ .project_content_title %h2.pull-left= t('.commits') - = project_analysis_timestamp(@project) - if @analysis.empty? = render 'projects/show/no_analysis_summary' @@ -16,12 +15,12 @@ @thirty_day_summary = NilAnalysisSummaryWithNa.new if @thirty_day_summary.nil? .col-md-12 - .well.margin_left_20.margin_right_20 + .oh-card.margin_left_20.margin_right_20 %center %table.data-table{ style: 'width: 60%' } %tbody %tr.border_bottom - %th{ width: '25%', align: 'right' }   + %th{ width: '25%' } %th.text-right{ width: '25%', align: 'right' }= t('.all_time') %th.text-right{ width: '25%', align: 'right' }= t('.12_month') %th.text-right{ width: '25%', align: 'right' }= t('.30_day') diff --git a/app/views/committers/_position.html.haml b/app/views/committers/_position.html.haml index 193d3319b..940d3499f 100644 --- a/app/views/committers/_position.html.haml +++ b/app/views/committers/_position.html.haml @@ -1,21 +1,19 @@ - project = position.project - name_fact = @name_facts_map[project.best_analysis_id] -.col-md-6.claim_form_box - .col-md-4 - .inner - .single_container - .group - = render 'unclaimed_tile', project: project, name_fact: name_fact - - .col-md-2.no_padding_left +.claim-position-card.oh-card + .claim-position-card__tile + .unclaimed-card__item + = render 'unclaimed_tile', project: project, name_fact: name_fact + .claim-position-card__fields %input{ type: 'hidden', value: project.id, name: 'positions[][project_id]' } - %input.textbox{ placeholder: t('.your_role', name: project.name), type: 'text', name: 'positions[][title]', - value: position.title } - %textarea{ placeholder: t('.description'), name: 'positions[][description]' }= position.description - - .clearfix - - - if position.errors.present? - .col-md-5 - %p.error= position.errors[:base] + .form-group + %label.claim-position-card__label= t('.your_role', name: project.name) + %input.form-control{ placeholder: t('.your_role', name: project.name), + type: 'text', name: 'positions[][title]', value: position.title } + .form-group + %label.claim-position-card__label= t('.description') + %textarea.form-control{ placeholder: t('.description'), + name: 'positions[][description]', rows: 3 }= position.description + - if position.errors.present? + %p.claim-position-card__error= position.errors[:base] diff --git a/app/views/committers/_unclaimed_tile.html.haml b/app/views/committers/_unclaimed_tile.html.haml index f2852dc5b..2a787602b 100644 --- a/app/views/committers/_unclaimed_tile.html.haml +++ b/app/views/committers/_unclaimed_tile.html.haml @@ -10,12 +10,10 @@ .logo %a{ href: project_path(project), title: project.name } - project_icon(project, :small, class: 'logo') -.commits{ style: "background-color: ##{bg_color}; color: ##{txt_color} !important", title: emails.join("\n") } - %span= name_fact.commits - .clearfix + %span.commit-count= name_fact.commits +.commits{ title: emails.join("\n") } %a{ href: project_contributor_path(project, contributor_id), style: 'display: inline-block;' } - = 'commit'.pluralize(name_fact.commits).to_s -.clearfix + = 'COMMITS' .project_link = link_to h(truncate(project.name, length: 14)), project_path(project), title: project.name diff --git a/app/views/committers/claim.html.haml b/app/views/committers/claim.html.haml index 616844b79..54b9f643d 100644 --- a/app/views/committers/claim.html.haml +++ b/app/views/committers/claim.html.haml @@ -3,21 +3,27 @@ - content_for(:html_title) { t('.title', current_user: current_user.nil? ? nil : "#{current_user.name} : ") } - page_context[:select_top_menu_nav] = :select_people -%h1.margin_bottom_15= t('.people') -%h2= t('.heading') -%h3.margin_top_15.margin_bottom_25 - = link_to t('.unclaimed_committer_ids'), committers_path -  :  - = link_to h(@name.name), committer_path(@name.id) - = t('.claim') -.clearfix +.committers_index + .account_header_inner + #account_name + %h1= t('.people') + .info_row + .info + %i.fa.fa-code + = t('.heading') + %h3.claim-breadcrumb + = link_to t('.unclaimed_committer_ids'), committers_path + %span.claim-breadcrumb__sep : + = link_to h(@name.name), committer_path(@name.id) + %span.claim-breadcrumb__action= t('.claim') -- if @positions.present? - .well.col-md-12.unclaimed_committers_box +.committers-page-body + - if @positions.present? = form_tag(save_claim_committer_path(@name.id), method: :post) do - = render partial: 'position', collection: @positions - .clearfix - .col-md-4.margin_bottom_10 - %input.btn.btn-primary.btn-small{ type: 'submit', value: t('.save_claim') } -- else - %p= t('.nothing_to_claim') + .claim-positions-grid + = render partial: 'position', collection: @positions + .claim-submit-row + %input.btn.btn-primary.unclaimed-card__btn{ type: 'submit', value: t('.save_claim') } + - else + .oh-card + %p.claim-empty= t('.nothing_to_claim') diff --git a/app/views/committers/index.html.haml b/app/views/committers/index.html.haml index 61ac377d5..11b7cab38 100644 --- a/app/views/committers/index.html.haml +++ b/app/views/committers/index.html.haml @@ -7,22 +7,29 @@ page_context[:select_footer_nav] = :unclaimed end -%h1.margin_bottom_15= t('.people') -%h3.margin_bottom_15= t('.heading') -= render 'shared/search_dingus', collection: @unclaimed_people, filter_type: :radiobutton, - total_count: @unclaimed_people_count +.committers_index + .account_header_inner + #account_name + %h1= t('.people') + .info_row + .info + %i.fa.fa-users + = t('.heading') +.committers-page-body + = render 'shared/search_dingus', collection: @unclaimed_people, filter_type: :radiobutton, + total_count: @unclaimed_people_count -- if @unclaimed_people.present? - - @unclaimed_people.each do |name_id, people| - = render 'people/unclaimed_person', people: people, name_id: name_id + - if @unclaimed_people.present? + - @unclaimed_people.each do |name_id, people| + = render 'people/unclaimed_person', people: people, name_id: name_id -- elsif params[:query].present? - = link_to t('.see_all_committers'), committers_path, class: 'btn btn-info btn-small' - .clearfix -- else - %p= t('.no_committer') + - elsif params[:query].present? + = link_to t('.see_all_committers'), committers_path, class: 'btn btn-info btn-small' + .clearfix + - else + %p= t('.no_committer') -- if @unclaimed_people_count > @unclaimed_people.size - - remaining = @unclaimed_people_count - @unclaimed_people.size - .unshown_committers_info - = t('.remaining_committers_count', remaining: remaining) + - if @unclaimed_people_count > @unclaimed_people.size + - remaining = @unclaimed_people_count - @unclaimed_people.size + .unshown_committers_info + = t('.remaining_committers_count', remaining: remaining) diff --git a/app/views/committers/show.html.haml b/app/views/committers/show.html.haml index 9f2ea9e68..57469f0a6 100644 --- a/app/views/committers/show.html.haml +++ b/app/views/committers/show.html.haml @@ -1,8 +1,21 @@ - content_for(:html_title) { html_title } - page_context[:select_top_menu_nav] = :select_people -%h1.margin_bottom_15= t('.people') -%h3.margin_bottom_15 - = link_to t('.heading'), committers_path -  : #{@name.name} +.account_header + .account_header_inner + .header_back_row + = link_to committers_path, class: 'header-back-link' do + %i.fa.fa-chevron-left + = t('.heading') + .header_flex_container + #account_icon.committer-icon-placeholder + %i.fa.fa-user + #account_header + #account_name + %h1= @name.name + .info_row + .info + %i.fa.fa-code + = t('.people') + = render 'people/unclaimed_person', name_id: @name.id, people: @people, from_show_page: true diff --git a/app/views/compare_repositories/index.html.haml b/app/views/compare_repositories/index.html.haml index 5332f58c3..64093900c 100644 --- a/app/views/compare_repositories/index.html.haml +++ b/app/views/compare_repositories/index.html.haml @@ -1,5 +1,6 @@ - content_for(:html_title) { t('compare_repositories_page_title') } -.compare_repositories.margin_left_10 - %h3= t('compare_repositories') - .chart.watermark440{ style: 'height:480px', 'datasrc' => compare_repositories_chart_url } +.compare_repositories + .container + %h3= t('compare_repositories') + .chart.watermark440{ style: 'height:480px', 'datasrc' => compare_repositories_chart_url } diff --git a/app/views/compares/_mobile_view.html.haml b/app/views/compares/_mobile_view.html.haml new file mode 100644 index 000000000..3859942b7 --- /dev/null +++ b/app/views/compares/_mobile_view.html.haml @@ -0,0 +1,320 @@ +.mobile-compare-view + -# Project Cards (Horizontal Scroll) + .mobile-project-cards + - @projects.each_with_index do |project, index| + - if project + .mobile-project-card + .project-card-header + .project-info + - project_icon(project) + .project-name= link_to(h(truncate_project_name(project.name, 20, link: true)), project_path(project)) + - link = compare_projects_path(proj_names.except("project_#{index}")) + %a.remove-project{ href: link, 'aria-label': t('compares.project_section.remove_project') } + %i.icon-remove + - else + .mobile-project-card.empty-slot + %form.autocomplete-submit{ action: compare_projects_path } + - @projects.each_with_index do |proj, i| + - next if project == proj + %input{ type: 'hidden', name: "project_#{i}", value: proj ? proj.name : '' } + - key = "mobile_project_#{index}" + %input.autocompletable{ type: 'text', id: key, name: "project_#{index}", "aria-label": t('compares.project_section.project_input_label', number: index + 1), + placeholder: t('compares.project_section.add_project'), data: { source: '/autocompletes/project', select: 'submitForm' } } + + -# Accordion Sections + .mobile-accordion + -# General Section + .accordion-section + %button.accordion-header{ type: 'button', 'data-section': 'general' } + %span= t('compares.projects.general') + %i.icon-chevron-down + .accordion-content + .metric-row + .metric-label= t('compares.projects.compare_table_activity_level') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/activity_level", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_data_quality') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/data_quality", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_homepage') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/homepage", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_license') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/license", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_cocomo') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/cocomo", locals: { project: project } + - else + .empty – + + -# All Time Statistics Section + .accordion-section + %button.accordion-header{ type: 'button', 'data-section': 'all-time' } + %span= t('compares.projects.all_time_statistics') + %i.icon-chevron-down + .accordion-content + .metric-row + .metric-label= t('compares.projects.compare_table_committers_all_time') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/committers_all_time", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_commit_count') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/commit_count", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_initial_commit') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/initial_commit", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_most_recent_commit') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/most_recent_commit", locals: { project: project } + - else + .empty – + + -# 12 Month Statistics Section + .accordion-section + %button.accordion-header{ type: 'button', 'data-section': 'twelve-month' } + %span= t('compares.projects.twelve_month_statistics') + %i.icon-chevron-down + .accordion-content + .metric-row + .metric-label= t('compares.projects.compare_table_twelve_months_committers') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/twelve_months_committers", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_twelve_months_commits') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/twelve_months_commits", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_twelve_months_files_modified') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/twelve_months_files_modified", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_twelve_months_lines_added') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/twelve_months_lines_added", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_twelve_months_lines_removed') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/twelve_months_lines_removed", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_year_over_year_commits') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/year_over_year_commits", locals: { project: project } + - else + .empty – + + -# 30 Day Statistics Section + .accordion-section + %button.accordion-header{ type: 'button', 'data-section': 'thirty-day' } + %span= t('compares.projects.thirty_day_statistics') + %i.icon-chevron-down + .accordion-content + .metric-row + .metric-label= t('compares.projects.compare_table_thirty_day_committers') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/thirty_day_committers", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_thirty_day_commits') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/thirty_day_commits", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_thirty_day_files_modified') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/thirty_day_files_modified", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_thirty_day_lines_added') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/thirty_day_lines_added", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_thirty_day_lines_removed') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/thirty_day_lines_removed", locals: { project: project } + - else + .empty – + + -# Code Analysis Section + .accordion-section + %button.accordion-header{ type: 'button', 'data-section': 'code-analysis' } + %span= t('compares.projects.code_analysis') + %i.icon-chevron-down + .accordion-content + .metric-row + .metric-label= t('compares.projects.compare_table_mostly_written_in') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/mostly_written_in", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_comments') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/comments", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_lines_of_code') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/lines_of_code", locals: { project: project } + - else + .empty – + + -# People Section + .accordion-section + %button.accordion-header{ type: 'button', 'data-section': 'people' } + %span= t('compares.projects.people') + %i.icon-chevron-down + .accordion-content + .metric-row + .metric-label= t('compares.projects.compare_table_managers') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/managers", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_users') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/users", locals: { project: project } + - else + .empty – + + .metric-row + .metric-label= t('compares.projects.compare_table_rating') + .metric-values + - @projects.each do |project| + .metric-value + - if project + != render partial: "compares/project_cells/rating", locals: { project: project } + - else + .empty – diff --git a/app/views/compares/_project_section.html.haml b/app/views/compares/_project_section.html.haml index d32594ba4..0af2c43ab 100644 --- a/app/views/compares/_project_section.html.haml +++ b/app/views/compares/_project_section.html.haml @@ -23,6 +23,6 @@ - next if project == proj %input{ type: 'hidden', name: "project_#{i}", value: proj ? proj.name : '' } - key = "project_#{index}" - %input.autocompletable{ type: 'text', id: key, name: key, width: 20, + %input.autocompletable{ type: 'text', id: key, name: key, width: 20, "aria-label": t('.project_input_label', number: index + 1), placeholder: t('.enter_name'), data: { source: '/autocompletes/project', select: 'submitForm' } } %input{ type: 'submit', value: t('.go'), name: "submit_#{index}", style: 'display: none;' } diff --git a/app/views/compares/projects.html.haml b/app/views/compares/projects.html.haml index 250c87c48..aba0251e1 100644 --- a/app/views/compares/projects.html.haml +++ b/app/views/compares/projects.html.haml @@ -4,15 +4,15 @@ proj_names = params.select { |k, _| k.to_s.match(/project/) }.permit(:project_0, :project_1, :project_2) .projects_compare - %h1.float_left.margin_left_10= t('.compare_projects') - - if @projects.any? - %a.btn.btn-mini.btn-primary.csv{ href: compare_projects_path(proj_names.merge(format: 'csv')) }= t('.export_to_csv') - .pull-right.col-md-6.social_share_compare_projects - = render partial: '/shared/add_this', locals: { full_text: t('.i_just_compared') } - .clearfix + .container + .header-wrapper + %h1= t('.compare_projects') + - if @projects.any? + %a.btn.btn-mini.btn-primary.csv{ href: compare_projects_path(proj_names.merge(format: 'csv')) }= t('.export_to_csv') + .clearfix - .margin_left_10 - .projects_compare + -# Desktop Table View + .compare-card %table.side_by_side = compare_section t('.general'), class: 'first', include_header: true = compare_row t('.compare_table_activity_level'), :activity_level @@ -52,3 +52,6 @@ = compare_row t('.compare_table_managers'), :managers = compare_row t('.compare_table_users'), :users = compare_row t('.compare_table_rating'), :rating + + -# Mobile Accordion View + = render partial: 'compares/mobile_view', locals: { proj_names: proj_names } diff --git a/app/views/contributions/_contributions.html.haml b/app/views/contributions/_contributions.html.haml index bb03597d7..0aa669a6d 100644 --- a/app/views/contributions/_contributions.html.haml +++ b/app/views/contributions/_contributions.html.haml @@ -1,57 +1,108 @@ -%table.table - %thead - %tr{ id: 'dingus-row' } - %td{ colspan: 10 } - - if search_bar - = render 'shared/search_dingus/commits_or_contributor_search', items: contributions, type: :contributions - - else -   - %tr - %th.col-md-3.center= t('.name') - %th{ width: '45px' }= t('.kudos') - %th.center{ width: '80px' }= t('.12_month_commits') - %th{ width: '80px' }= t('.all_commits') - %th= t('.trend') - %th= t('.language') - %th= t('.first_commit') - %th= t('.last_commit') - %tbody - - contributions.each do |contribution| - :ruby - person_name = contribution.person.person_name - fact = contribution.contributor_fact - contributor_path = project_contributor_path(@project, contribution) +// Desktop table view +.desktop-only + %table.table + %thead %tr - %td - = link_to contributor_path, class: 'pull-left' do - = avatar_img_for(contribution.person) + %th.col-md-3.center= t('.name') + %th{ width: '45px' }= t('.kudos') + %th.center{ width: '80px' }= t('.12_month_commits') + %th{ width: '80px' }= t('.all_commits') + %th= t('.trend') + %th= t('.language') + %th= t('.first_commit') + %th= t('.last_commit') + %tbody + - contributions.each do |contribution| + :ruby + person_name = contribution.person.person_name + fact = contribution.contributor_fact + contributor_path = project_contributor_path(@project, contribution) + %tr + %td + = link_to contributor_path, class: 'pull-left' do + = avatar_img_for(contribution.person) - = link_to contributor_path, class: 'avatar_name pull-left col-md-10 padding_left_0', title: person_name do - = h truncate(person_name, length: 23) - %span.small.contribution_title.pull-left - - if contribution.position.title.present? - = t('.position_title', title: contribution.position.title) - - elsif contribution.position.present? && !contribution.position.active? - = t('.inactive') - - elsif @project.active_managers.include?(contribution.person.account) - = t('.manager') - %td - - rank = contribution.person.kudo_rank || 1 - = image_tag "icons/sm_laurel_#{rank}.png", alt: "KudoRank #{rank}" - %td.center - = fact.twelve_month_commits - %td - = fact.commits - %td - = image_tag commits_spark_project_contributor_url(project_id: @project.to_param, id: fact.name_id), - width: 179, height: 32, align: 'left' if fact.name_id - %td - = fact.primary_language.nice_name - %td - - if fact.first_checkin - %abbr.date{ title: fact.first_checkin }= t('.ago', duration: time_ago_in_words(fact.first_checkin)) - %td - - if fact.last_checkin - %abbr.date{ title: fact.last_checkin }= t('.ago', duration: time_ago_in_words(fact.last_checkin)) + = link_to contributor_path, class: 'avatar_name pull-left col-md-10 padding_left_0', title: person_name do + = h truncate(person_name, length: 23) + %span.small.contribution_title.pull-left + - if contribution.position.title.present? + = t('.position_title', title: contribution.position.title) + - elsif contribution.position.present? && !contribution.position.active? + = t('.inactive') + - elsif @project.active_managers.include?(contribution.person.account) + = t('.manager') + %td + - rank = contribution.person.kudo_rank || 1 + = image_tag "icons/sm_laurel_#{rank}.png", alt: "KudoRank #{rank}" + %td.center + = fact.twelve_month_commits + %td + = fact.commits + %td + = image_tag commits_spark_project_contributor_url(project_id: @project.to_param, id: fact.name_id), + width: 179, height: 32, alt: '' if fact.name_id + %td + = fact.primary_language.nice_name + %td + - if fact.first_checkin + %abbr.date{ title: fact.first_checkin }= t('.ago', duration: time_ago_in_words(fact.first_checkin)) + %td + - if fact.last_checkin + %abbr.date{ title: fact.last_checkin }= t('.ago', duration: time_ago_in_words(fact.last_checkin)) + +// Mobile card view +.contributors-cards-container.mobile-only + - contributions.each do |contribution| + :ruby + person_name = contribution.person.person_name + fact = contribution.contributor_fact + contributor_path = project_contributor_path(@project, contribution) + .contributor-card-item + .card-item-header + = link_to contributor_path, class: 'contributor-link' do + = avatar_img_for(contribution.person) + .contributor-name= h truncate(person_name, length: 30) + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand details' } + %i.fa.fa-chevron-down + + .card-item-content + .content-row + .content-item + .label= t('.12_month_commits') + .value= fact.twelve_month_commits + .content-item + .label= t('.primary_language') + .value= fact.primary_language.nice_name + + .content-row + .content-item + .label= t('.all_commits') + .value= fact.commits + .content-item + .label= t('.kudos') + .value + - rank = contribution.person.kudo_rank || 1 + = image_tag "icons/sm_laurel_#{rank}.png", alt: "KudoRank #{rank}" + = rank + + .content-row + .content-item + .label= t('.first_commit') + .value + - if fact.first_checkin + %abbr.date{ title: fact.first_checkin }= t('.ago', duration: time_ago_in_words(fact.first_checkin)) + .content-item + .label= t('.last_commit') + .value + - if fact.last_checkin + %abbr.date{ title: fact.last_checkin }= t('.ago', duration: time_ago_in_words(fact.last_checkin)) + + - if fact.name_id + .content-row.full-width + .content-item + .label= t('.trend') + .trend-image + = image_tag commits_spark_project_contributor_url(project_id: @project.to_param, id: fact.name_id), + width: 179, height: 32, alt: '' = render partial: 'shared/alert', locals: { message: t('.no_data') } if contributions.empty? diff --git a/app/views/contributions/_newest_contributions.html.haml b/app/views/contributions/_newest_contributions.html.haml index f973e1e97..9fb4f36d4 100644 --- a/app/views/contributions/_newest_contributions.html.haml +++ b/app/views/contributions/_newest_contributions.html.haml @@ -1,7 +1,5 @@ %table.table.table-striped.table-condensed %thead - %tr{ id: 'dingus-row' } - %td{ colspan: '3' }   %tr %th.col-md-3.center= t('.name') %th.col-md-1.center= t('.commits') diff --git a/app/views/contributions/index.html.haml b/app/views/contributions/index.html.haml index 8c910e0b4..b09e1288f 100644 --- a/app/views/contributions/index.html.haml +++ b/app/views/contributions/index.html.haml @@ -8,12 +8,12 @@ %h2.pull-left = link_to t('.contributors').html_safe, summary_project_contributors_path(@project) = t('.listing').html_safe - = project_analysis_timestamp(@project) .clearfix - if @project.best_analysis.present? - = render partial: 'contributions', locals: { contributions: @contributions, search_bar: true } - = will_paginate @contributions + = render 'shared/search_dingus/commits_or_contributor_search', items: @contributions, type: :contributions, total_count: @contributions.total_entries + = render partial: 'contributions', locals: { contributions: @contributions } + = render 'shared/modern_pagination', collection: @contributions - else = render partial: 'projects/show/no_analysis_summary', locals: { analysis: @project.best_analysis } diff --git a/app/views/contributions/show.html.haml b/app/views/contributions/show.html.haml index b91926851..1c6c81f05 100644 --- a/app/views/contributions/show.html.haml +++ b/app/views/contributions/show.html.haml @@ -12,42 +12,41 @@ = @project.name = committer_name -%h2.pull-left.contributor_name.margin_top_20 - = succeed ' : ' do - = link_to t('.contributors'), summary_project_contributors_path(@project) - = committer_name - -= project_analysis_timestamp(@project) - -.clearfix - -= render partial: 'contributions/show/activity', locals: { committer: committer_name, account: account, fact: fact } - -.col-md-3.pull-right - %h4.margin_top_15= t('.recent_kudos') - %p - = t('.given_by', name: @project.name) - - @recent_kudos.each do |kudo| - %p.nomargin.margin_left_15 - = link_to h(kudo.sender.name), account_url(kudo.sender), id: "kudo_received_link_#{kudo.sender.login}" - = image_tag "icons/tn_laurel_#{kudo.sender.kudo_rank}.png", alt: "KudoRank #{kudo.sender.kudo_rank}" - - unless kudo.message.blank? - %p.margin_left_25{ id: "kudo_received_#{kudo.sender.login}" } - "#{kudo.message}" - - - if @recent_kudos.empty? - %p.margin_left_15 - = t('.no_kudos') - - .padding_one_top - = render partial: 'contributions/show/kudo_button', locals: { contribution: @contribution } if fact.present? -.clearfix - -.col-md-12 - = render partial: 'contributions/show/unclaimed_position', locals: { contribution: @contribution } - -.col-md-12 - = render partial: 'commits_timeline', locals: { contribution: @contribution } - -.col-md-12 - = render partial: 'contributions/show/languages', locals: { contribution: @contribution, name_fact: fact } +#contribution-show-page + .contribution-header + %h2.contribution-title + = link_to t('.contributors'), summary_project_contributors_path(@project) + %span.contribution-title__sep › + = committer_name + + .row + .col-md-9.col-sm-8.col-xs-12 + = render partial: 'contributions/show/activity', locals: { committer: committer_name, account: account, fact: fact } + + .col-md-3.col-sm-4.col-xs-12 + .oh-card.contribution-kudos + %h4.contribution-kudos__title= t('.recent_kudos') + %p.contribution-kudos__subtitle= t('.given_by', name: @project.name) + - @recent_kudos.each do |kudo| + .contribution-kudo-item + = link_to h(kudo.sender.name), account_url(kudo.sender), id: "kudo_received_link_#{kudo.sender.login}", class: 'contribution-kudo-item__name' + = image_tag "icons/tn_laurel_#{kudo.sender.kudo_rank}.png", alt: "KudoRank #{kudo.sender.kudo_rank}", class: 'contribution-kudo-item__rank' + - unless kudo.message.blank? + %p.contribution-kudo-item__message{ id: "kudo_received_#{kudo.sender.login}" } + "#{kudo.message}" + - if @recent_kudos.empty? + %p.contribution-kudos__empty= t('.no_kudos') + .contribution-kudos__action + = render partial: 'contributions/show/kudo_button', locals: { contribution: @contribution } if fact.present? + + .row + .col-md-12 + = render partial: 'contributions/show/unclaimed_position', locals: { contribution: @contribution } + + .row + .col-md-12 + = render partial: 'commits_timeline', locals: { contribution: @contribution } + + .row + .col-md-12 + = render partial: 'contributions/show/languages', locals: { contribution: @contribution, name_fact: fact } diff --git a/app/views/contributions/show/_activity.html.haml b/app/views/contributions/show/_activity.html.haml index 41ef376f0..58416d291 100644 --- a/app/views/contributions/show/_activity.html.haml +++ b/app/views/contributions/show/_activity.html.haml @@ -1,48 +1,39 @@ -.well.pull-left.col-md-9{ style: 'padding: 10px 0px 19px 19px' } - %h4 - = t('.activity_on', name: @project.name, committer: committer) - = link_to h(t('.see_full_profile', name: committer)), account_path(account) if account - .committer-summary - = image_tag avatar_img_path(@contribution.person, 64), class: 'pull-left avatar', style: 'margin-bottom:65px;' - - unless fact.nil? - - spark_url = commits_compound_spark_project_contributor_url(project_id: @project.to_param, id: fact.name_id) - .col-md-3 - %span= t('.all_commits') - %span.pull-right - = fact.commits - .clearfix - %span= t('.12_mo_commits') - %span.pull-right - = fact.twelve_month_commits - .clearfix - %span= t('.30_day_commits') - %span.pull-right - = fact.thirty_day_commits - .clearfix - - .pull-left{ style: 'width:165px; margin-left:215px;' } - %span= t('.overall_rank') - %span.pull-right{ style: 'margin-top:-8px;' } - - person = @contribution.person - - path = person.nil? ? '#' : rankings_people_path(show: person.id) - = link_to avatar_small_laurels(person && person.kudo_rank), path, class: 'laurel' - .clearfix - %span= t('.first_commit') - %span.pull-right - = fact.first_checkin.to_s(:dmy) - .clearfix - %span= t('.last_commit') - %span.pull-right - = fact.last_checkin.to_s(:dmy) - .clearfix - - .padding_one_top.col-md-10 - %span= t('.scm_names') - %span= @contribution.scm_names.map(&:name).join(', ') - - .padding_one_top.col-md-10 - %span= t('.history') - .col-md-10.pull-right{ style: 'width: 80%' } - = image_tag spark_url, width: 430, height: 39 - .clearfix - .clearfix +.oh-card.contribution-activity + .contribution-activity__header + %h4.contribution-activity__title= t('.activity_on', name: @project.name, committer: committer) + - if account + = link_to h(t('.see_full_profile', name: committer)), account_path(account), class: 'contribution-activity__profile-link' + - unless fact.nil? + - spark_url = commits_compound_spark_project_contributor_url(project_id: @project.to_param, id: fact.name_id) + .contribution-activity__body + .contribution-activity__avatar + = image_tag avatar_img_path(@contribution.person, 64), class: 'contribution-activity__avatar-img', alt: h(@contribution.person.try(:name).to_s) + .contribution-activity__stats + .contribution-stat + %span.contribution-stat__label= t('.all_commits') + %span.contribution-stat__value= fact.commits + .contribution-stat + %span.contribution-stat__label= t('.12_mo_commits') + %span.contribution-stat__value= fact.twelve_month_commits + .contribution-stat + %span.contribution-stat__label= t('.30_day_commits') + %span.contribution-stat__value= fact.thirty_day_commits + .contribution-activity__rank + - person = @contribution.person + - path = person.nil? ? '#' : rankings_people_path(show: person.id) + .contribution-stat + %span.contribution-stat__label= t('.overall_rank') + %span.contribution-stat__value= link_to avatar_small_laurels(person && person.kudo_rank), path, class: 'laurel' + .contribution-stat + %span.contribution-stat__label= t('.first_commit') + %span.contribution-stat__value= fact.first_checkin.to_s(:dmy) + .contribution-stat + %span.contribution-stat__label= t('.last_commit') + %span.contribution-stat__value= fact.last_checkin.to_s(:dmy) + .contribution-activity__meta + %span.contribution-activity__scm-label= t('.scm_names') + %span.contribution-activity__scm-value= @contribution.scm_names.map(&:name).join(', ') + .contribution-activity__history + %span.contribution-activity__history-label= t('.history') + .contribution-activity__spark + = image_tag spark_url, class: 'contribution-activity__spark-img', alt: '' diff --git a/app/views/contributions/show/_kudo_button.html.haml b/app/views/contributions/show/_kudo_button.html.haml index cb7433266..a2c02b2cb 100644 --- a/app/views/contributions/show/_kudo_button.html.haml +++ b/app/views/contributions/show/_kudo_button.html.haml @@ -12,5 +12,9 @@ %icon.icon-undo = t('.undo_kudo') - else - = link_to t('.give_kudo'), '#', class: 'btn kudo-btn btn-primary btn-mini', - onclick: "tb_show('#{kudo_for}', '#{action}', false); return false;" + = link_to '#', class: 'btn kudo-btn btn-primary btn-mini', + onclick: "tb_show('#{kudo_for}', '#{action}', false); return false;" do + %svg.kudo-btn-icon{ xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' } + %path{ d: 'M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3z' } + %path{ d: 'M7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3' } + = t('.give_kudo') diff --git a/app/views/contributions/show/_languages.html.haml b/app/views/contributions/show/_languages.html.haml index 64065bfcd..de20e77fe 100644 --- a/app/views/contributions/show/_languages.html.haml +++ b/app/views/contributions/show/_languages.html.haml @@ -1,50 +1,49 @@ :ruby facts = name_fact.name_language_facts -%h4= t('.title') +%h4.contribution-languages__title= t('.title') - if name_fact && facts.any? - %table.table.table-striped.center-block{ style: 'width:70%' } - %tr - %th{ width: '3%' } - %th{ width: '25%', scope: 'col' }= t('.language') - %th{ width: '12%', scope: 'col' }= t('.aggregate') - %th{ width: '10%', scope: 'col' }= t('.total_commits') - %th{ width: '12%', scope: 'col' }= t('.total_changed') - %th{ width: '10%', scope: 'col' }= t('.ratio') - - - facts.each do |nlf| + .contribution-languages__table-wrap + %table.table.table-striped %tr - %td{ style: "background-color: ##{language_color(nlf.language.name)}" } -   - %td - = link_to nlf.language.nice_name, language_path(nlf.language) - %td.center= months_or_years(nlf.total_months) - %td.center= number_with_delimiter(nlf.total_commits) - %td.center= number_with_delimiter(nlf.total_activity_lines) - %td.center - = nlf.comment_ratio ? number_with_precision(nlf.comment_ratio.to_f * 100.0, precision: 1).to_s + '%' : '-' + %th{ width: '3%' } + %th{ width: '25%', scope: 'col' }= t('.language') + %th{ width: '12%', scope: 'col' }= t('.aggregate') + %th{ width: '10%', scope: 'col' }= t('.total_commits') + %th{ width: '12%', scope: 'col' }= t('.total_changed') + %th{ width: '10%', scope: 'col' }= t('.ratio') - - if facts.size > 1 - %tr - %td - %td= t('.all_languages') - %td.center= months_or_years(name_fact.man_months) - - if defined?(contribution) && contribution + - facts.each do |nlf| + %tr + %td{ style: "background-color: ##{language_color(nlf.language.name)}" } +   + %td + = link_to nlf.language.nice_name, language_path(nlf.language) + %td.center= months_or_years(nlf.total_months) + %td.center= number_with_delimiter(nlf.total_commits) + %td.center= number_with_delimiter(nlf.total_activity_lines) %td.center - = link_to number_with_delimiter(name_fact.commits), - project_contributor_commits_path(contribution.project, contribution) - - else - %td.center= number_with_delimiter(name_fact.commits) - %td.center= number_with_delimiter(facts.map(&:total_activity_lines).sum) - %td.center - - if name_fact.comment_ratio - = number_with_precision(name_fact.comment_ratio.to_f * 100.0, precision: 1).to_s + '%' + = nlf.comment_ratio ? number_with_precision(nlf.comment_ratio.to_f * 100.0, precision: 1).to_s + '%' : '-' + + - if facts.size > 1 + %tr + %td + %td= t('.all_languages') + %td.center= months_or_years(name_fact.man_months) + - if defined?(contribution) && contribution + %td.center + = link_to number_with_delimiter(name_fact.commits), + project_contributor_commits_path(contribution.project, contribution) - else - '-' + %td.center= number_with_delimiter(name_fact.commits) + %td.center= number_with_delimiter(facts.map(&:total_activity_lines).sum) + %td.center + - if name_fact.comment_ratio + = number_with_precision(name_fact.comment_ratio.to_f * 100.0, precision: 1).to_s + '%' + - else + '-' - else - .row - .col-md-7 - .well - %p.inset_middle= t('.not_measured') + .oh-card + %p.inset_middle= t('.not_measured') diff --git a/app/views/contributions/show/_unclaimed_position.html.haml b/app/views/contributions/show/_unclaimed_position.html.haml index 84e7ea2b4..7df54257a 100644 --- a/app/views/contributions/show/_unclaimed_position.html.haml +++ b/app/views/contributions/show/_unclaimed_position.html.haml @@ -1,24 +1,19 @@ - if contribution.position.blank? - .well - %p{ style: 'font-size: 18px' } - %i.icon-exclamation-sign   - = t('.do_you_know') - .row.margin_right_10.margin_left_40 - .offset1.margin_left_40 - = t('.help_out') - .padding_one_top.offset1 - .col-md-6.margin_left_40.padding_left_40 - .col-md-0 - = link_to_claim_position contribution, t('.claim') - .col-md-12 - = t('.are_you') - %br - = t('.add') - .col-md-5 - .col-md-0 - = link_to t('.send_invite'), new_contributor_invite_path(contribution), class: 'btn' - .col-md-3.no_padding{ style: 'width:230px' } - = t('.know') - %br - = t('.invite_him') - .clearfix + .oh-card.contribution-unclaimed + .contribution-unclaimed__header + %i.icon-exclamation-sign.contribution-unclaimed__icon + %span.contribution-unclaimed__title= t('.do_you_know') + %p.contribution-unclaimed__help= t('.help_out') + .contribution-unclaimed__actions + .contribution-unclaimed__action + = link_to_claim_position contribution, t('.claim') + %p.contribution-unclaimed__desc + = t('.are_you') + %br + = t('.add') + .contribution-unclaimed__action + = link_to t('.send_invite'), new_contributor_invite_path(contribution), class: 'btn contribution-unclaimed__invite-btn' + %p.contribution-unclaimed__desc + = t('.know') + %br + = t('.invite_him') diff --git a/app/views/contributions/summary.html.haml b/app/views/contributions/summary.html.haml index 2e0c5cca7..9eec3d816 100644 --- a/app/views/contributions/summary.html.haml +++ b/app/views/contributions/summary.html.haml @@ -8,8 +8,7 @@ = @project.name .project_content_title - %h2.pull-left= t('.contributors') - = project_analysis_timestamp(@project) + %h1.pull-left= t('.contributors') .clearfix - if @analysis.present? @@ -22,21 +21,36 @@ .chart.watermark440#committer_history_chart{ datasrc: committer_history_chart, style: 'height:320px' } .clearfix - .col-md-12 - %h4= link_to t('.newest_contributors'), project_contributors_path(@project, sort: 'newest') - .col-md-6 - = render partial: 'newest_contributions', locals: { contributions: @newest_contributions[0..4] } - - if @newest_contributions.length > 5 - .col-md-6 - = render partial: 'newest_contributions', locals: { contributions: @newest_contributions[5..-1] } - .clearfix - .clearfix + .contributors-section.newest-contributors-section + %h2.section-title + %svg.section-icon{width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width" => "2"} + %rect{width: "18", height: "18", x: "3", y: "4", rx: "2", ry: "2"} + %line{x1: "16", y1: "2", x2: "16", y2: "6"} + %line{x1: "8", y1: "2", x2: "8", y2: "6"} + %line{x1: "3", y1: "10", x2: "21", y2: "10"} + = link_to t('.newest_contributors'), project_contributors_path(@project, sort: 'newest'), class: 'section-title-link' + .two-column-layout.desktop-only + .column-left + .col-md-6 + = render partial: 'newest_contributions', locals: { contributions: @newest_contributions[0..4] } + - if @newest_contributions.length > 5 + .column-right + .col-md-6 + = render partial: 'newest_contributions', locals: { contributions: @newest_contributions[5..-1] } + .mobile-only + = render partial: 'newest_contributions', locals: { contributions: @newest_contributions } - .col-md-12.top-committers - %h4= link_to t('.top_contributions'), project_contributors_path(@project) - = render partial: 'contributions', locals: { contributions: @top_contributions, search_bar: false } - %p - = link_to t('.see_all'), project_contributors_path(@project), class: 'btn btn-info btn-small' + .contributors-section.top-contributors-section + %h2.section-title + %svg.section-icon{width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width" => "2"} + %path{d: "M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"} + = link_to t('.top_contributions'), project_contributors_path(@project), class: 'section-title-link' + .col-md-12.top-committers + = render partial: 'contributions', locals: { contributions: @top_contributions } + %p.see-all-container + = link_to project_contributors_path(@project), class: 'btn' do + %i.fa.fa-users + %span= t('.see_all') .clearfix - else = render partial: 'projects/show/no_analysis_summary' diff --git a/app/views/duplicates/_fields.html.haml b/app/views/duplicates/_fields.html.haml index eab4b2700..8cd08fe4e 100644 --- a/app/views/duplicates/_fields.html.haml +++ b/app/views/duplicates/_fields.html.haml @@ -1,31 +1,28 @@ - good = @duplicate.good_project -%fieldset - %legend= t('.legend_html', name: truncate_project_name(@duplicate.bad_project.name)) - .control-group.clearfix - %label.control-label.required= t('.good_label_html', name: truncate_project_name(@duplicate.bad_project.name)) - .controls - = text_field_tag :good_project, good.try(:name), class: 'autocompletable', style: 'width: 95%;', - data: { source: '/autocompletes/project_duplicates', select: 'duplicatesSelect' } - .hidden#new_duplicate_spinner= image_tag 'spinner.gif' - - error_tag @duplicate, :good_project +%fieldset.duplicate-fieldset + %legend.duplicate-legend= t('.legend_html', name: truncate_project_name(@duplicate.bad_project.name)) - .control-group.clearfix#good_project_url_label{ class: good ? '' : ' hidden' } - %label.control-label= t('.original_url') - .controls - - if good - %a#good_project_url{ base: projects_url, href: project_url(good) }= project_url(good) - - else - %a#good_project_url{ base: projects_url, href: '#' } + .duplicate-field-group + %label.duplicate-label.required{ for: 'good_project' }= t('.good_label_html', name: truncate_project_name(@duplicate.bad_project.name)) + = text_field_tag :good_project, good.try(:name), id: 'good_project', class: 'form-control duplicate-input', + data: { source: '/autocompletes/project_duplicates', select: 'duplicatesSelect' } + .hidden#new_duplicate_spinner= image_tag 'spinner.gif', alt: '' + - error_tag @duplicate, :good_project - .control-group.clearfix - %label.control-label= t('.comment') - .controls - = preserve do - = form.text_area(:comment, rows: 10, cols: 80, max_length: 1000, style: 'width: 95%;') + .duplicate-field-group#good_project_url_label{ class: good ? '' : 'hidden' } + %label.duplicate-label= t('.original_url') + - if good + %a.duplicate-url#good_project_url{ base: projects_url, href: project_url(good) }= project_url(good) + - else + %a.duplicate-url#good_project_url{ base: projects_url, href: '#' } + + .duplicate-field-group + %label.duplicate-label{ for: 'duplicate_comment' }= t('.comment') + = form.text_area(:comment, id: 'duplicate_comment', rows: 6, max_length: 1000, class: 'form-control duplicate-textarea') = form.hidden_field :bad_project_id = form.hidden_field :good_project_id - .actions - %input.btn.btn-primary{ type: 'submit', value: button_text } + .duplicate-actions + %input.btn.btn-primary.duplicate-submit{ type: 'submit', value: button_text } diff --git a/app/views/duplicates/_instructions.html.haml b/app/views/duplicates/_instructions.html.haml index e4a562550..33cf96325 100644 --- a/app/views/duplicates/_instructions.html.haml +++ b/app/views/duplicates/_instructions.html.haml @@ -1,13 +1,12 @@ -.col-md-6 - %h4.nomargin +.oh-card.duplicate-instructions + %h4.duplicate-instructions__title %span.soft= t(:about) = t('.header') - - %ul{ style: 'margin-left: 30px;' } + %ul.duplicate-instructions__list %li= t('.help1') %li = t('.help2') - %ul{ style: 'margin-left: 30px;' } + %ul - link = link_to(html_escape(@project.name), project_path(@project), title: html_escape(@project.name)) %li!= t('.help3', project: link) %li= t('.help4', name: @project.name) diff --git a/app/views/duplicates/edit.html.haml b/app/views/duplicates/edit.html.haml index 8b686c80f..f1836f38f 100644 --- a/app/views/duplicates/edit.html.haml +++ b/app/views/duplicates/edit.html.haml @@ -5,7 +5,7 @@ .clearfix .col-md-8.col-md-offset-2 - .well + .oh-card = form_for @duplicate, url: project_duplicate_path(@project, @duplicate), html: { method: 'put' } do |form| = render partial: 'fields', locals: { form: form, button_text: t('.button_text') } diff --git a/app/views/duplicates/index.html.haml b/app/views/duplicates/index.html.haml index 193f5cb2d..57c9bb4b6 100644 --- a/app/views/duplicates/index.html.haml +++ b/app/views/duplicates/index.html.haml @@ -12,8 +12,8 @@ .tab-content .tab-pane.fade.in.active#unresolved-duplicates = render collection: @unresolved_duplicates, partial: 'duplicate', locals: { resolved: false } - = will_paginate @unresolved_duplicates + = render 'shared/modern_pagination', collection: @unresolved_duplicates .tab-pane.fade#resolved-duplicates = render collection: @resolved_duplicates, partial: 'duplicate', locals: { resolved: true } - = will_paginate @resolved_duplicates + = render 'shared/modern_pagination', collection: @resolved_duplicates diff --git a/app/views/duplicates/new.html.haml b/app/views/duplicates/new.html.haml index 50b00974a..07441cb22 100644 --- a/app/views/duplicates/new.html.haml +++ b/app/views/duplicates/new.html.haml @@ -1,12 +1,14 @@ - content_for(:html_title) { t('.page_title', name: @project.name) } -%h2.float_left= t('.page_header') -= project_analysis_timestamp(@project) +#duplicate-new-page + .duplicate-page-header + %h2.duplicate-page-title= t('.page_header') -.clearfix -.col-md-8.col-md-offset-2 - .well - = form_for @duplicate, url: project_duplicates_path(@project) do |form| - = render partial: 'fields', locals: { form: form, button_text: t('.button_text') } + .row + .col-md-7.col-sm-9.col-xs-12.center-block + .oh-card + = form_for @duplicate, url: project_duplicates_path(@project) do |form| + = render partial: 'fields', locals: { form: form, button_text: t('.button_text') } -= render partial: 'instructions' + .col-md-4.col-sm-10.col-xs-12.center-block + = render partial: 'instructions' diff --git a/app/views/edits/_index.html.haml b/app/views/edits/_index.html.haml index 297030e29..186325ac6 100644 --- a/app/views/edits/_index.html.haml +++ b/app/views/edits/_index.html.haml @@ -1,11 +1,10 @@ .clearfix += render 'shared/search_dingus', collection: @edits, filter_type: :checkbox, no_match_found_type: :flash + #edits_list %table.table.table-striped.alt.table-condensed %tbody - %tr{ id: 'dingus-row' } - %td - = render 'shared/search_dingus', collection: @edits, filter_type: :checkbox, no_match_found_type: :flash = render partial: 'edit', collection: @edits if @edits.present? - if params[:query].present? && @edits.blank? @@ -14,8 +13,8 @@ .clearfix %h4= t('.no_edits_found') -= will_paginate @edits, class: 'oh_pagination' += render 'shared/modern_pagination', collection: @edits .modal.fade#edit-details{ role: 'dialog', tabindex: -1 } .modal-dialog{ role: 'document' } - .edit_spinner= image_tag 'spinner.gif' + .edit_spinner= image_tag 'spinner.gif', alt: '' diff --git a/app/views/edits/_undo_block.html.haml b/app/views/edits/_undo_block.html.haml index 578132134..aa4852d17 100644 --- a/app/views/edits/_undo_block.html.haml +++ b/app/views/edits/_undo_block.html.haml @@ -2,11 +2,11 @@ - can_redo = edit.allow_redo? - target_css = edit.target_type.downcase if edit.type == 'CreateEdit' - if edit.target.edit_authorized? && can_redo - %a.btn.btn-mini.btn-warning{ href: '#', class: "#{needs_login_or_verification_or_default(:redo)} #{target_css}" } + %a.btn.btn-medium.btn-warning{ href: '#', class: "#{needs_login_or_verification_or_default(:redo)} #{target_css}" } %i.icon-repeat= t('.redo') - else - btn_text = " #{can_redo ? t('.redo') : t('.cant_redo')}" - = disabled_button btn_text, class: 'btn-mini btn-warning' + = disabled_button btn_text, class: 'btn-medium btn-warning' - if edit.undone_by .info = link_to h(edit.undoer.name), account_url(edit.undoer) @@ -15,11 +15,11 @@ - else - can_undo = edit.allow_undo? - if edit.target.edit_authorized? && can_undo - %a.btn.btn-mini.btn-primary{ href: '#', class: "#{needs_login_or_verification_or_default(:undo)} #{target_css}" } + %a.btn.btn-medium.btn-primary{ href: '#', class: "#{needs_login_or_verification_or_default(:undo)} #{target_css}" } %i.icon-undo= t('.undo') - else - btn_text = " #{can_undo ? t('.undo') : t('.cant_undo')}" - = disabled_button btn_text, class: 'disabled btn-mini btn-primary' + = disabled_button btn_text, class: 'disabled btn-medium btn-primary' - unless can_undo .info= t('.no_earlier_value') - if edit.undone_by diff --git a/app/views/edits/index_account.html.haml b/app/views/edits/index_account.html.haml index c0f70bf62..08b3a98d6 100644 --- a/app/views/edits/index_account.html.haml +++ b/app/views/edits/index_account.html.haml @@ -8,15 +8,21 @@ #edits_account %h2.float_left = link_to t(:settings), settings_account_path(@parent) -  : #{t('.openhub_edit_history')} +  : #{t('.openhub_edit_history')} = render partial: 'index' - .col-md-6 - %h4.margin_bottom_0 - .soft.inline= t(:about) - = t('.edit_history') - %ul.margin_left_50 - %li= t('.help_1') - %li= t('.help_2') - %li= t('.help_3') + .col-md-12 + .about-account-basics-card + .card-header + %h4.card-title + %span.soft= t(:about) + = t('.edit_history') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + .card-content + .about-content-wrapper + %ul + %li= t('.help_1') + %li= t('.help_2') + %li= t('.help_3') diff --git a/app/views/edits/index_license.html.haml b/app/views/edits/index_license.html.haml index d9c8bc64a..83b27742d 100644 --- a/app/views/edits/index_license.html.haml +++ b/app/views/edits/index_license.html.haml @@ -1,11 +1,16 @@ %meta{ name: 'ROBOTS', content: 'NOINDEX, NOFOLLOW' } - content_for(:html_title) { t('.page_title', name: @parent.vanity_url) } -#license - %h1.margin_bottom_20 +#license-edit-page + .license-edit-breadcrumb = link_to t('.licenses'), licenses_url -  :  + %span.license-edit-sep / = link_to h(truncate(@parent.name, length: 42)), license_url(@parent), title: @parent.name -  : #{t('.openhub_edit_history')} + %span.license-edit-sep / + %span= t('.openhub_edit_history') + .license-edit-header + %h1.license-edit-title + %i.fa.fa-history + = t('.openhub_edit_history') -= render partial: 'index' + = render partial: 'index' diff --git a/app/views/edits/index_organization.html.haml b/app/views/edits/index_organization.html.haml index 6446760f6..b1fdd3eb5 100644 --- a/app/views/edits/index_organization.html.haml +++ b/app/views/edits/index_organization.html.haml @@ -2,19 +2,23 @@ - content_for(:html_title) { t('.page_title') } - page_context[:select_footer_nav] = nil -%h2.pull-left +%h2.float-left = link_to t(:settings), settings_organization_path(@parent) -  : #{t('.openhub_edit_history')} +  : #{t('.openhub_edit_history')} = render partial: 'index' -.margin_left_20 - %h4.margin_bottom_0 - .soft.inline= t(:about) - = t('.openhub_edit_history') - - .margin_left_25.col-md-6 - %ul - %li= t('.help_1') - %li= t('.help_2') - %li= t('.help_3') +.col-md-12 + .about-account-basics-card + .card-header + %h4.card-title + %span.soft= t(:about) + = t('.openhub_edit_history') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + .card-content + .about-content-wrapper + %ul + %li= t('.help_1') + %li= t('.help_2') + %li= t('.help_3') diff --git a/app/views/edits/index_project.html.haml b/app/views/edits/index_project.html.haml index dcdfe5022..781915e32 100644 --- a/app/views/edits/index_project.html.haml +++ b/app/views/edits/index_project.html.haml @@ -8,16 +8,22 @@ %h2.pull-left = link_to t(:settings), settings_project_path(@parent)  : #{t('.openhub_edit_history')} - = project_analysis_timestamp(@parent) = render partial: 'index' -%h4.margin_bottom_0 - .soft.inline= t(:about) - = t('.openhub_edit_history') +.about-project-basics-card + .card-header + %h4.card-title + %span.soft= t('.about') + = t('.openhub_edit_history') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + + .card-content + .about-content-wrapper + .col-md-6.margin_left_30 + %ul + %li= t('.help_1') + %li= t('.help_2') + %li= t('.help_3') -.margin_left_25.col-md-5 - %ul - %li= t('.help_1') - %li= t('.help_2') - %li= t('.help_3') diff --git a/app/views/enlistments/_about_code_locations.html.haml b/app/views/enlistments/_about_code_locations.html.haml index a8135276c..c819dff4d 100644 --- a/app/views/enlistments/_about_code_locations.html.haml +++ b/app/views/enlistments/_about_code_locations.html.haml @@ -1,23 +1,30 @@ -%h4.margin_top_10.margin_bottom_0 - %span.soft= t('.about') - = t('.code_locations') -.col-md-5.margin_left_30 - %ul - %li= t('.description1') - %li= t('.description2') - %li= t('.description3') - %li= t('.description4') - %li - = t('.description5') - %strong= t('.description6') - = t('.description7') -.col-md-5.margin_left_20 - %ul - %li - = t('.description8') - %ul.margin_left_30 - %li - = t('.description9') - %li - = t('.description10') - %li= t('.description11') +.about-code-locations-card + .card-header + %h4.card-title + %span.soft= t('.about') + = t('.code_locations') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + + .card-content + .about-content-wrapper + .col-md-5.margin_left_30 + %ul + %li= t('.description1') + %li= t('.description2') + %li= t('.description3') + %li= t('.description4') + %li + = t('.description5') + %strong= t('.description6') + = t('.description7') + .col-md-5.margin_left_20 + %ul + %li + = t('.description8') + %ul.margin_left_30 + %li + = t('.description9') + %li + = t('.description10') + %li= t('.description11') diff --git a/app/views/enlistments/_enlistment.html.haml b/app/views/enlistments/_enlistment.html.haml index 100a81dad..5d72125e9 100644 --- a/app/views/enlistments/_enlistment.html.haml +++ b/app/views/enlistments/_enlistment.html.haml @@ -5,7 +5,7 @@ - if current_user_is_admin? = link_to code_location_admin_url(enlistment.code_location_id), target: '_blank' do %span.glyphicon.glyphicon-link - - job_id = enlistment.fis_code_location.last_job_id + - job_id = enlistment.fis_code_location&.last_job_id - fis_job = FisJob.find_by(id: job_id) - description = job_id ? FailureGroupApi.failure_group_description(job_id) : '' - if enlistment.code_location.do_not_fetch diff --git a/app/views/enlistments/_fields.html.haml b/app/views/enlistments/_fields.html.haml index 6414fce86..04ea691a3 100644 --- a/app/views/enlistments/_fields.html.haml +++ b/app/views/enlistments/_fields.html.haml @@ -1,6 +1,6 @@ %fieldset .control-group - %label.control-label.required= t('.scm_type') + %label.control-label.required{ for: 'repository_type' }= t('.scm_type') .controls.chosen#value_select %select.chzn-select.value-select{ name: 'code_location[scm_type]', id: 'repository_type' } = options_for_select_type(@code_location.scm_type) @@ -8,15 +8,15 @@ .control-group .controls .default-url-tags - %label.control-label.required= t('.enter_url') - = f.text_field :url, class: 'col-md-7', value: params.dig(:code_location, :url) + %label.control-label.required{ for: 'code_location_url' }= t('.enter_url') + = f.text_field :url, class: 'form-control', value: params.dig(:code_location, :url) - error_tag @code_location, :url .github.scm_info{ style: 'display: none;' } - %label.control-label.required= t('.enter_github_username') + %label.control-label.required{ for: 'code_location_github_url' }= t('.enter_github_username') .input-group %span.input-group-addon https://github.com/ - = f.text_field :url, class: 'form-control' + = f.text_field :url, class: 'form-control', id: 'code_location_github_url' - error_tag @code_location, :url .clearfix @@ -56,9 +56,11 @@ .control-group.warning %label.control-label= t('.if_required') .controls - = f.text_field :username, placeholder: t('.username'), style: 'display:block;' + %label.control-label.sr-only{ for: 'code_location_username' }= t('.username') + = f.text_field :username, placeholder: t('.username'), class: 'form-control' - error_tag @code_location, :username - = f.text_field :password, placeholder: t('.password'), style: 'display:block;' + %label.control-label.sr-only{ for: 'code_location_password' }= t('.password') + = f.text_field :password, placeholder: t('.password'), class: 'form-control' - error_tag @code_location, :password %p.help-block.col-md-7 %strong= t('.warning') @@ -67,9 +69,9 @@ .cvs.scm_info{ style: 'display: none;' } .control-group - %label.cotrol-label.required= t('.cvs_module_name') + %label.control-label.required{ for: 'code_location_cvs_branch' }= t('.cvs_module_name') .controls - = f.text_field :branch + = f.text_field :branch, class: 'form-control', id: 'code_location_cvs_branch' - error_tag @code_location, :branch %p.help-block = t('.enter_cvs') @@ -78,9 +80,9 @@ .git.scm_info{ style: 'display: none;' } .control-group - %label.control-label.required= t('.enter_git') + %label.control-label.required{ for: 'code_location_git_branch' }= t('.enter_git') .controls - = f.text_field :branch, value: params.dig(:code_location, :branch) + = f.text_field :branch, class: 'form-control', id: 'code_location_git_branch', value: params.dig(:code_location, :branch) - error_tag @code_location, :branch_name %p.help-block = t('.enter_git_name') @@ -96,7 +98,7 @@ - if logged_in? && @project.edit_authorized? = submit_tag t('.save_code_location'), class: 'btn btn-primary submit' %p.spinner{ style: 'display: none;' } - = image_tag 'spinner.gif' + = image_tag 'spinner.gif', alt: '' = t('.check_server_connection') - else = disabled_button t('.save_code_location'), class: 'btn-primary' diff --git a/app/views/enlistments/_no_enlistment.html.haml b/app/views/enlistments/_no_enlistment.html.haml index d221536d3..2d24fa726 100644 --- a/app/views/enlistments/_no_enlistment.html.haml +++ b/app/views/enlistments/_no_enlistment.html.haml @@ -15,7 +15,7 @@ %p= t('.help2') -.col-md-11.well +.col-md-11.oh-card = form_for @project, html: { method: :put, class: 'normal' } do |f| = hidden_field_tag 'original_missing_source', @project.missing_source %fieldset diff --git a/app/views/enlistments/edit.html.haml b/app/views/enlistments/edit.html.haml index 14ac90525..d39a9ca7d 100644 --- a/app/views/enlistments/edit.html.haml +++ b/app/views/enlistments/edit.html.haml @@ -1,77 +1,83 @@ - content_for(:html_title) { t('.html_title', name: @project.name) } - page_context[:select_footer_nav] = nil -%h2.pull-left - = link_to t('settings'), settings_project_path(@project) -  :  - = link_to t('.code_locations'), project_enlistments_path(@project) - = t('.ignored_files') -= project_analysis_timestamp(@project) +#enlistment-edit-page + .row.margin_left_5 + .col-md-10.col-md-offset-1 + %h3 + = link_to t('settings'), settings_project_path(@project) +  :  + = link_to t('.code_locations'), project_enlistments_path(@project) + = t('.ignored_files') + .oh-card + = form_for [@project, @enlistment], html: { method: :put } do |f| + %fieldset + .control-group + %label.control-label{ for: 'enlistment_ignore' } + = t('.ignored_files_for') + %strong.code-location-url= @enlistment.code_location.nice_url + .controls + = find_and_preserve(f.text_area(:ignore, rows: 20, class: 'form-control', + style: 'font-family: monospace;')) + - error_tag @enlistment, :ignore + %p.help-block + = t('.description1') + .actions + %input.btn.btn-primary{ type: 'submit', value: t('.save_ignored_files') } .clearfix -.col-md-offset-1.col-md-11.padding_right_50 - .well - = form_for [@project, @enlistment], html: { method: :put } do |f| - %fieldset - .control-group - %label.control-label - = t('.ignored_files_for') - %strong= @enlistment.code_location.nice_url - .controls - = find_and_preserve(f.text_area(:ignore, rows: 20, cols: 40, - style: 'width: 730px; font-family: monospace;')) - - error_tag @enlistment, :ignore - %p.help-block - = t('.description1') - .actions - %input.btn.btn-primary{ type: 'submit', value: t('.save_ignored_files') } +.about-code-locations-card + .card-header + %h4.card-title + %span.soft= t('.about') + = t('.code_locations_ignored') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down -.clearfix + .card-content + .about-content-wrapper + .col-md-5.margin_left_30 + %p= t('.syntax_tips') + %ul + %li + = t('.description6') + %code= t('.description7') + %li + = t('.description8') + %code= t('.description9') + = t('.description10') + %code= t('.description11') + = t('.description12') + %li + = t('.description13') + %code= t('.description14') + = t('.description15') + %code= t('.description16') + = t('.description17') + %li + = t('.description18') + %code= t('.description19') + = t('.description20') + %li + = t('.description21') + %li + = t('.description22') + %code= t('.description23') + = t('.description24') + = t('.description25') + %li + = t('.description26') + %code= t('.description27') + = t('.description28') + %code= t('.description29') + = t('.description30') + .col-md-5.margin_left_20 + %p= t('.example') + - other_examples = '' + - if @examples.present? + - other_examples << t('.some_random_files') + - @examples.each do |enlistment| + - other_examples << t('.disallow_html', enlistment: enlistment) + %pre + = t('.excluded_directory_html', other_examples: other_examples) -%h4.nomargin - %span.soft= t('.about') - = t('.code_locations_ignored') -.col-md-5 - %p= t('.syntax_tips') - %ul - %li - = t('.description6') - %code= t('.description7') - %li - = t('.description8') - %code= t('.description9') - = t('.description10') - %code= t('.description11') - = t('.description12') - %li - = t('.description13') - %code= t('.description14') - = t('.description15') - %code= t('.description16') - = t('.description17') - %li - = t('.description18') - %code= t('.description19') - = t('.description20') - %li - = t('.description21') - %li - = t('.description22') - %code= t('.description23') - = t('.description24') - = t('.description25') - %li - = t('.description26') - %code= t('.description27') - = t('.description28') - %code= t('.description29') - = t('.description30') -.col-md-5 - %p= t('.example') - - other_examples = '' - - if @examples.present? - - other_examples << t('.some_random_files') - - @examples.each do |enlistment| - - other_examples << t('.disallow_html', enlistment: enlistment) - %pre - = t('.excluded_directory_html', other_examples: other_examples) diff --git a/app/views/enlistments/edit_allowed_files.html.haml b/app/views/enlistments/edit_allowed_files.html.haml index 9eaacb840..ead6c4561 100644 --- a/app/views/enlistments/edit_allowed_files.html.haml +++ b/app/views/enlistments/edit_allowed_files.html.haml @@ -1,42 +1,48 @@ - content_for(:html_title) { t('.html_title', name: @project.name) } - page_context[:select_footer_nav] = nil -%h2.pull-left - = link_to t('settings'), settings_project_path(@project) -  :  - = link_to t('.code_locations'), project_enlistments_path(@project) - = t('.allow_files') -= project_analysis_timestamp(@project) +#enlistment-edit-page + .row.margin_left_5 + .col-md-10.col-md-offset-1 + %h3 + = link_to t('settings'), settings_project_path(@project) +  :  + = link_to t('.code_locations'), project_enlistments_path(@project) + = t('.allow_files') + .oh-card + = form_for [@project, @enlistment], html: { method: :put } do |f| + %fieldset + .control-group + %label.control-label{ for: 'enlistment_allowed_fyles' } + = t('.allowed_files_for') + %strong.code-location-url= @enlistment.code_location.nice_url + .controls + = find_and_preserve(f.text_area(:allowed_fyles, rows: 20, class: 'form-control', + style: 'font-family: monospace;')) + - error_tag @enlistment, :allowed_fyles + %p.help-block + = t('.description1') + .actions + %input.btn.btn-primary{ type: 'submit', value: t('.save_allowed_files') } .clearfix -.col-md-offset-1.col-md-11.padding_right_50 - .well - = form_for [@project, @enlistment], html: { method: :put } do |f| - %fieldset - .control-group - %label.control-label - = t('.allowed_files_for') - %strong= @enlistment.code_location.nice_url - .controls - = find_and_preserve(f.text_area(:allowed_fyles, rows: 20, cols: 40, - style: 'width: 730px; font-family: monospace;')) - - error_tag @enlistment, :allowed_fyles - %p.help-block - = t('.description1') - .actions - %input.btn.btn-primary{ type: 'submit', value: t('.save_allowed_files') } +.about-code-locations-card + .card-header + %h4.card-title + %span.soft= t('.about') + = t('.code_locations_allowed') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down -.clearfix + .card-content + .about-content-wrapper + .col-md-5.margin_left_30 + %p= t('.example') + - other_examples = '' + - if @examples.present? + - other_examples << t('.some_random_files') + - @examples.each do |enlistment| + - other_examples << t('.allow_html', enlistment: enlistment) + %pre + = t('.included_directory_html', other_examples: other_examples) -%h4.nomargin - %span.soft= t('.about') - = t('.code_locations_allowed') -.col-md-5 - %p= t('.example') - - other_examples = '' - - if @examples.present? - - other_examples << t('.some_random_files') - - @examples.each do |enlistment| - - other_examples << t('.allow_html', enlistment: enlistment) - %pre - = t('.included_directory_html', other_examples: other_examples) diff --git a/app/views/enlistments/index.html.haml b/app/views/enlistments/index.html.haml index 3af2e23ee..dc7915973 100644 --- a/app/views/enlistments/index.html.haml +++ b/app/views/enlistments/index.html.haml @@ -8,93 +8,176 @@ = link_to t('settings'), settings_project_path(@project) = t('.code_locations') - if current_user_is_admin? - = link_to code_location_ids_admin_url(@enlistments.pluck(:code_location_id)), target: '_blank', title: 'Code locations admin for current page', rel: 'tooltip' do - %span.glyphicon.glyphicon-link - = project_analysis_timestamp(@project) + = link_to code_location_ids_admin_url(@enlistments.pluck(:code_location_id)), target: '_blank', title: 'Code locations admin for current page', rel: 'tooltip', aria: { label: 'Code locations admin for current page' } do + %span.glyphicon.glyphicon-link{ aria: { hidden: true } } .clearfix - if sidekiq_work_in_progress? - .alert.alert-info.alert-block - %h5.alert-heading.nomargin= t('.currently_importing') + .enlistment-banner.enlistment-banner--info + .enlistment-banner__strip + .enlistment-banner__inner + .enlistment-banner__icon + %i.icon-time + .enlistment-banner__content + %h4= t('.currently_importing') - if flash[:show_first_enlistment_alert] - .alert.alert-info.alert-block - %h4.alert-heading.nomargin= t('.alert_info_heading', name: @project.name) - %p= t('.alert_info_description') + .enlistment-banner.enlistment-banner--info + .enlistment-banner__strip + .enlistment-banner__inner + .enlistment-banner__icon + %i.icon-ok-circle + .enlistment-banner__content + %h4= t('.alert_info_heading', name: @project.name) + %p= t('.alert_info_description') - if @stale_jobs_report.any? - .alert.alert-warning.alert-block - %h4.alert-heading.nomargin= t('.alert_warn_heading') - %p= t('.alert_warn_description1') - = t('.alert_warn_description2') - = link_to t('.help_forum'), 'https://community.blackduck.com/s/topic/0TO2H000000gHS1WAM/black-duck-open-hub-help' - \. - %p - = t('.alert_warn_description3_html') + .enlistment-banner.enlistment-banner--warning + .enlistment-banner__strip + .enlistment-banner__inner + .enlistment-banner__icon + %i.icon-warning-sign + .enlistment-banner__content + %h4= t('.alert_warn_heading') + %p= t('.alert_warn_description1') + %p + = t('.alert_warn_description2') + = link_to t('.help_forum'), 'https://community.blackduck.com/s/topic/0TO2H000000gHS1WAM/black-duck-open-hub-help', class: 'enlistment-banner__link', target: '_blank', rel: 'noopener noreferrer' + = '.' + %p= t('.alert_warn_description3_html') - reportable_group_names = @stale_jobs_report.keys.map(&:to_s) & FailureGroup::REPORTABLE - if reportable_group_names.any? - .alert.alert-info.alert-block - %h5.alert-heading.nomargin= t('.failure.heading') - %ul.padding_left_10 - - reportable_group_names.each do |failure_group_name| - - key = "enlistments.index.failure.#{failure_group_name}" - %li.font-1em= t(key) if I18n.exists?(key) + .enlistment-banner.enlistment-banner--danger + .enlistment-banner__strip + .enlistment-banner__inner + .enlistment-banner__icon + %i.icon-exclamation-sign + .enlistment-banner__content + %h4= t('.failure.heading') + %ul + - reportable_group_names.each do |failure_group_name| + - key = "enlistments.index.failure.#{failure_group_name}" + %li= t(key) if I18n.exists?(key) - if @project.enlistments.exists? && api_access_available - %table.table.table-striped - %thead - %tr{ id: 'dingus-row' } - %td{ colspan: '7' } - .margin_top_10.margin_bottom_10 - %form.form-inline - .form-group.margin_right_10.padding_left_10 - - if @enlistments.respond_to?(:current_page) && !@enlistments.length.zero? - %label.paginate= "Showing page #{number_with_delimiter(@enlistments.current_page)} of #{number_with_delimiter(@enlistments.total_pages)}" - - elsif @enlistments.size.zero? - %label.paginate= t('.count_status', current_count: @enlistments.length, - total_count: number_with_delimiter(0)) - .form-group.margin_right_10 - %label Search   - = text_field_tag :query, params[:query] - - %button.btn.btn-refresh{ type: 'Submit', style: 'height: 34px; border-top: 10px' } - %i.icon-refresh - .form-group.margin_right_10 - %label - = check_box_tag(:dnf, '1', (params[:dnf] == '1' ? true : false), style: 'margin-top: 1px', onChange: 'submit()') - = label_tag(:dnf, 'Show unprocessable sources') - - .form-group.pull-right.padding_right_10 - #sort_by - %label= 'Sort by' - = select_tag :sort, options_for_select(SORT_OPTIONS['enlistments'][:options], - params[:sort] || SORT_OPTIONS['enlistments'][:default]), - class: 'chzn-select', onchange: 'this.form.submit()', style: 'width: 150px;' - - %tr - %th= t('.repository_url') - %th= t('.scm_type') - %th= t('.update_status') - %th= t('.ignored_files') - %th= t('.allowed_files') - %th= t('.actions') - %tbody - = render partial: 'enlistment', collection: @enlistments, locals: { has_permission: has_permission } + .search-filter-bar + %form + .search-filter-content + = render 'shared/search_dingus/page_entries_info', collection: @enlistments, total_count: @enlistments.total_entries + + = render 'shared/search_dingus/search_bar', search_type: :default + + %label.checkbox + = check_box_tag(:dnf, '1', (params[:dnf] == '1' ? true : false), onChange: 'this.form.submit()') + = t('.show_unprocessable_sources') + + = render 'shared/search_dingus/sort', sort_context: :enlistments, filter_type: nil + + // Desktop table view + .desktop-only + %table.table + %thead + %tr + %th= t('.repository_url') + %th= t('.scm_type') + %th= t('.update_status') + %th= t('.ignored_files') + %th= t('.allowed_files') + %th= t('.actions') + %tbody + = render partial: 'enlistment', collection: @enlistments, locals: { has_permission: has_permission } + + // Mobile card view + .enlistments-cards-container.mobile-only + - @enlistments.each do |enlistment| + - url = enlistment.code_location.url + - job_id = enlistment.fis_code_location.last_job_id + - fis_job = FisJob.find_by(id: job_id) + - description = job_id ? FailureGroupApi.failure_group_description(job_id) : '' + - ass = enlistment.analysis_sloc_set + .enlistment-card-item + .card-item-header + .repository-url + = url =~ /^http/ ? link_to(url, url, target: '_blank') : url + - if current_user_is_admin? + = link_to code_location_admin_url(enlistment.code_location_id), target: '_blank' do + %span.glyphicon.glyphicon-link + - if enlistment.code_location.do_not_fetch + - if description.present? + %p.text-danger Error: #{description} + - else + %p.text-danger= t('.dnf') + - elsif !enlistment.code_location.do_not_fetch && fis_job && fis_job.retry_count > 1 && fis_job.wait_until != nil + - if description.present? + %p.text-danger Error: #{description} + %p= enlistment.code_location.branch + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand details' } + %i.fa.fa-chevron-down + + .card-item-content + .content-row + .content-item + .label= t('.scm_type') + .value + - scm_type = enlistment.code_location.scm_name_in_english + - if scm_type.present? + = scm_type + + .content-item + .label= t('.update_status') + .value + - status = CodeLocationJobProgress.new(enlistment).message + - if status.present? + = status + + .content-row + .content-item + .label= t('.ignored_files') + .value + - if ass + - if ass.ignored_fyle_count.to_i.zero? + = t('.description3') + - else + = t('.description1') + = pluralize(ass.ignored_fyle_count, t('.file')) + = t('.description2') + + .content-item + .label= t('.allowed_files') + .value + - if ass + - if ass.allowed_fyle_count.to_i.zero? + = t('.description3') + - else + = t('.description4') + = pluralize(ass.allowed_fyle_count, t('.file')) + = t('.description2') + + .content-row.full-width + - if has_permission + = icon_button(edit_project_enlistment_path(@project, enlistment), icon: 'pencil', text: t('.edit_ignored'), size: 'mini', type: 'primary') + - if current_user_is_admin? + = icon_button(edit_allowed_files_project_enlistment_path(@project, enlistment), icon: 'pencil', text: t('.edit_allowed'), size: 'mini', type: 'primary') + = icon_button(project_enlistment_path(@project, enlistment), icon: 'trash', text: t('.remove'), size: 'mini', type: 'danger', method: :delete, data: { confirm: t('.confirm') }) + - else + = disabled_button bootstrap_icon('icon-pencil', t('.edit_ignored')), class: 'btn-mini btn-primary' + = disabled_button bootstrap_icon('icon-trash', t('.remove')), class: 'btn-mini btn-danger pull-right' - elsif api_access_available = render '/enlistments/no_enlistment' - else = render partial: 'shared/api_outage' -= will_paginate @enlistments += render 'shared/modern_pagination', collection: @enlistments - if @enlistments.empty? && params[:query].present? = render partial: 'shared/alert', locals: { message: t('.no_match') } - if @project.enlistments.exists? && api_access_available - - if logged_in? && @project.edit_authorized? - = link_to t('.new_code_location'), new_project_enlistment_path(@project), class: 'btn btn-primary' - - else - = icon_button(new_project_enlistment_url(@project), text: t('.new_code_location'), class: 'btn btn-primary') + .new-code-location-container + - if logged_in? && @project.edit_authorized? + = link_to t('.new_code_location'), new_project_enlistment_path(@project), class: 'btn btn-primary' + - else + = icon_button(new_project_enlistment_url(@project), text: t('.new_code_location'), class: 'btn btn-primary') = render partial: 'about_code_locations' diff --git a/app/views/enlistments/new.html.haml b/app/views/enlistments/new.html.haml index 903ff1f01..f9b5a9bd8 100644 --- a/app/views/enlistments/new.html.haml +++ b/app/views/enlistments/new.html.haml @@ -1,16 +1,16 @@ - content_for(:html_title) { t('.page_title', name: @project.name) } - page_context[:select_footer_nav] = nil -%h2.pull-left - = link_to t('settings'), settings_project_path(@project) -  :  - = link_to t('.code_locations'), project_enlistments_path(@project) - = t('.new') -= project_analysis_timestamp(@project) - -.col-md-8.col-md-offset-2 - .well.enlistment - = form_for CodeLocation.new, url: project_enlistments_url, html: { method: 'post' } do |f| - = render partial: 'fields', locals: { f: f } +#enlistment-edit-page + .row.margin_left_5 + .col-md-10.col-md-offset-1 + %h3 + = link_to t('settings'), settings_project_path(@project) +  :  + = link_to t('.code_locations'), project_enlistments_path(@project) + = t('.new') + .oh-card.enlistment + = form_for CodeLocation.new, url: project_enlistments_url, html: { method: 'post' } do |f| + = render partial: 'fields', locals: { f: f } .clearfix = render partial: 'about_code_locations' diff --git a/app/views/explore/_hot_projects.html.haml b/app/views/explore/_hot_projects.html.haml index 07e4c341e..eb358667f 100644 --- a/app/views/explore/_hot_projects.html.haml +++ b/app/views/explore/_hot_projects.html.haml @@ -1,20 +1,79 @@ -.pull-right.col-md-8.margin_top_20 - %h2.pull-left= t('.title') - %form{ action: '', rel: 'sort_filter' } - .pull-right.nav-select-container#sort_by - %label.pull-left{ style: 'line-height: 28px' } - = t('.filter') -   - = select_tag :lang, options_for_select(@languages, params[:lang]), class: 'chzn-select col-md-2 pull-right' - .clearfix - %table.table.table-striped.center-block#hot_projects - %tr - %th{ width: '5%' }= t('.rank') - %th{ width: '5%' } - %th{ width: '35%' }= t('.name') - %th{ width: '15%' }= t('.claimed') - %th.center{ width: '8%' }= t('.pai') - %th.center{ width: '12%' } - = link_to t('.hotness_score'), blog_url_for(:hotness_score), class: 'meta', target: '_blank' - = render 'hot_projects_list' - .clearfix +/ Header with Language Filter (outside table card, matching Figma) +.hot-projects-header + %h2.section-title= t('.title', default: 'Hot Projects on Open Hub') + + .language-filter-wrapper + %span.filter-label= t('.filter_by', default: 'Filter by:') + + .language-dropdown + %button.language-toggle{ type: 'button', data: { toggle: 'language' } } + %span.language-selected= params[:lang].present? ? params[:lang] : t('.all_languages', default: 'All Languages') + %span.chevron-down ▼ + + .language-dropdown-menu + - if defined?(@languages) && @languages.present? + - @languages.each do |lang_name, lang_code| + = link_to lang_name, lang_code.blank? ? projects_explores_path : projects_explores_path(lang: lang_code), class: "language-option#{(lang_code.blank? && params[:lang].blank?) || params[:lang] == lang_code ? ' active' : ''}" + - else + = link_to t('.all_languages', default: 'All Languages'), projects_explores_path, class: "language-option#{params[:lang].blank? ? ' active' : ''}" + = link_to 'C++', projects_explores_path(lang: 'c++'), class: "language-option#{params[:lang] == 'c++' ? ' active' : ''}" + = link_to 'PHP', projects_explores_path(lang: 'php'), class: "language-option#{params[:lang] == 'php' ? ' active' : ''}" + = link_to 'Python', projects_explores_path(lang: 'python'), class: "language-option#{params[:lang] == 'python' ? ' active' : ''}" + = link_to 'JavaScript', projects_explores_path(lang: 'javascript'), class: "language-option#{params[:lang] == 'javascript' ? ' active' : ''}" + = link_to 'Java', projects_explores_path(lang: 'java'), class: "language-option#{params[:lang] == 'java' ? ' active' : ''}" + +/ Hot Projects Table Card +.hot-projects-container + .hot-projects-table-wrapper + / Table Header + .projects-table-header + .header-cell.rank-col= t('.rank', default: 'Rank') + .header-cell.icon-col + .header-cell.name-col= t('.name', default: 'Name') + .header-cell.claimed-col= t('.claimed_by', default: 'Claimed By') + .header-cell.pai-col= t('.pai', default: 'PAI') + .header-cell.hotness-col + = link_to t('.hotness_score', default: 'Hotness'), blog_url_for(:hotness_score), class: 'meta', target: '_blank' + + / Table Body + .projects-table-body + - if defined?(@projects) && @projects.present? + - @projects.each_with_index do |project, index| + - project_name = truncate(project.name, length: 26) + - org = project.organization + - org_name = truncate(org.name, length: 30) if org + + .project-row + / Rank + .table-cell.rank-col + %span.rank-number= (index + 1) + + / Icon + .table-cell.icon-col + .project-icon + = project.decorate.icon(:small) + + / Name + .table-cell.name-col + = link_to h(project_name), project_path(project), class: 'project-name-link' + + / Claimed By + .table-cell.claimed-col + - if org + = link_to h(org_name), organization_path(org), class: 'org-link', title: org.name + - else + %span.no-org — + + / PAI Indicator + .table-cell.pai-col + .pai-indicator + .pai-tooltip-wrapper{ 'data-tooltip': project_activity_text(project, true) } + - project_activity_level_class(project, :twentyfive) + + / Hotness Score + .table-cell.hotness-col + %span.hotness-score= number_with_precision(project.best_analysis.angle, precision: 1) + + - else + .no-projects + %p.no-projects-message= t('.no_matching_records', default: 'No matching projects found') diff --git a/app/views/explore/_most_active_orgs.html.haml b/app/views/explore/_most_active_orgs.html.haml index b1d3eb16f..8e69e55f8 100644 --- a/app/views/explore/_most_active_orgs.html.haml +++ b/app/views/explore/_most_active_orgs.html.haml @@ -1,20 +1,22 @@ -.well.col-md-4.margin_left_5#most-active-orgs - %h2.title= t('.title') - - @most_active_orgs.each do |org_thirty_day_activity| - - org = org_thirty_day_activity.organization - .row - .col-md-3 - = link_to org.decorate.icon(:med), organization_path(org), title: org.name, - style: 'text-decoration: none', class: 'logo' - .col-md-9 - = link_to h(truncate(org.name, length: 24)), organization_path(org), title: org.name - .margin_top_10 - = link_to org_thirty_day_activity.commits_per_affiliate, - organization_path(org, view: 'affiliated_committers') - %i.small= t('.commits_or_affiliates') - .row.no_margin_bottom#active-org-timestamp - .col-md-6 - %p= t('.within_30_days') - .col-md-6.pull-right - %p.pull-right.margin_right_10 - = t '.last_calculated', duration: time_ago_in_words(@most_active_orgs.first.created_at) if @most_active_orgs.first +#most-active-orgs + .most-active-card + .card-header + %h2= t('.title') + .card-body + - @most_active_orgs.each do |org_thirty_day_activity| + - org = org_thirty_day_activity.organization + .org-item + .org-logo + = link_to org.decorate.icon(:med), organization_path(org), title: org.name, class: 'logo' + .org-info + = link_to h(truncate(org.name, length: 24)), organization_path(org), title: org.name, class: 'org-name' + .org-stats + = link_to org_thirty_day_activity.commits_per_affiliate, + organization_path(org, view: 'affiliated_committers'), class: 'commits-value' + %span.stat-label= t('.commits_or_affiliates') + .card-footer + .footer-text + %p= t('.within_30_days') + .footer-timestamp + %p + = t '.last_calculated', duration: time_ago_in_words(@most_active_orgs.first.created_at) if @most_active_orgs.first diff --git a/app/views/explore/_newest_orgs.html.haml b/app/views/explore/_newest_orgs.html.haml index ed45ccd0b..6cdeb9a7e 100644 --- a/app/views/explore/_newest_orgs.html.haml +++ b/app/views/explore/_newest_orgs.html.haml @@ -1,14 +1,21 @@ -.col-md-4#newest-orgs - %h2= t '.title' - - @newest_orgs.each do |org| - .row - .col-md-3 - = link_to org.decorate.icon(:med), organization_path(org), style: 'text-decoration: none', title: org.name - .col-md-9 - = link_to h(truncate(org.name, length: 24)), organization_path(org), title: org.name - .margin_top_10 - = link_to t('.projects_count', count: org.projects_count), - organization_path(org, view: 'portfolio_projects') - %span.small= t('.added', duration: time_ago_in_words(org.updated_at)) - %p - = link_to t('.more_html'), organizations_path(sort: 'newest') +#newest-orgs + .newest-orgs-card + .card-header + %h2= t('.title') + .card-body + - @newest_orgs.each do |org| + .org-item + .org-logo + = link_to org.decorate.icon(:med), organization_path(org), title: org.name, class: 'logo' + .org-info + = link_to h(truncate(org.name, length: 24)), organization_path(org), title: org.name, class: 'org-name' + .org-stats + %span.projects-count + - if org.projects_count > 0 + = link_to t('.projects_count', count: org.projects_count), + organization_path(org, view: 'portfolio_projects'), class: 'projects-value' + - else + %span.projects-value= t('.projects_count', count: org.projects_count) + %span.added-time= t('.added', duration: time_ago_in_words(org.updated_at)) + .card-footer + = link_to t('.more_html'), organizations_path(sort: 'newest'), class: 'more-link' diff --git a/app/views/explore/_orgs_by_30_day_commit_volume.html.haml b/app/views/explore/_orgs_by_30_day_commit_volume.html.haml index d020ca4d3..c1345bc1a 100644 --- a/app/views/explore/_orgs_by_30_day_commit_volume.html.haml +++ b/app/views/explore/_orgs_by_30_day_commit_volume.html.haml @@ -1,16 +1,56 @@ -.col-md-8.no_padding#orgs_by_30_days_volume - %h2.pull-left= t('.title') - .pull-right.nav-select-container#sort_by - %label.pull-left#filter!= t('.filter') - = select_tag(:org_type, options_for_select(OrgThirtyDayActivity::SORT_TYPES), - class: 'chzn-select pull-right', style: 'width:126px') - .clearfix +#orgs_by_30_days_volume + -# Desktop Title and Filter + .desktop-header + %h2.pull-left= t('.title') + .pull-right#sort_by + .sort-section + %label!= t('.filter') + :ruby + current_filter = params[:org_type] || 'all_orgs' + selected_label = OrgThirtyDayActivity::SORT_TYPES.find { |opt| opt[1] == current_filter }&.first || OrgThirtyDayActivity::SORT_TYPES.first.first + .custom-sort-dropdown + %button.sort-dropdown-btn{ type: 'button', aria: { expanded: 'false', controls: 'org-type-menu', label: t('.filter_type_label') } } + %span.selection{ data: { value: current_filter } }= selected_label + %i.fa.fa-chevron-down + .dropdown-menu.sort-dropdown-menu{ id: 'org-type-menu', role: 'menu' } + - OrgThirtyDayActivity::SORT_TYPES.each do |label, value| + %button.sort-dropdown-item{ type: 'button', data: { value: value }, role: 'menuitem', class: (value == current_filter ? 'active' : '') }= label + %input{ type: 'hidden', name: 'org_type', value: current_filter, class: 'sort-value-input', id: 'org_type' } + .clearfix + #org_filter_no_result{ class: @org_by_30_day_commits.empty? ? '' : 'hidden' } %h5 %i.icon-warning-sign = t '.no_results' .busy.hidden#commit_volume_loader - %table.table.table-condensed{ class: @org_by_30_day_commits.empty? ? 'hidden' : '' } + + -# Mobile Card View + .mobile-org-container{ class: @org_by_30_day_commits.empty? ? 'hidden' : '' } + .orgs-card + .card-body-mobile + - @org_by_30_day_commits.each do |org_30_day_activity| + - org = org_30_day_activity.organization + .org-card + .org-card-header + = link_to org.decorate.icon(:small), organization_path(org), title: org.name, class: 'org-card-icon' + = link_to h(truncate(org.name, length: 24)), organization_path(org), title: org.name, class: 'org-card-name' + %span.org-card-size= org_30_day_activity.decorate.project_count_text + .org-card-body + .org-card-row + .org-card-label= t '.type' + .org-card-value= org.org_type_label + .org-card-row + .org-card-label= t '.projects' + .org-card-value= org_30_day_activity.project_count + .org-card-row + .org-card-label= t '.affiliates' + .org-card-value= org_30_day_activity.affiliate_count + .org-card-row + .org-card-label= t '30_day_commits' + .org-card-value= org_30_day_activity.thirty_day_commit_count + + -# Desktop Table View + %table.table.table-condensed.desktop-org-table{ class: @org_by_30_day_commits.empty? ? 'hidden' : '' } .margin_top_15 %thead %tr#header diff --git a/app/views/explore/_project_demographics.html.haml b/app/views/explore/_project_demographics.html.haml index 6a01fbb8e..5f578696e 100644 --- a/app/views/explore/_project_demographics.html.haml +++ b/app/views/explore/_project_demographics.html.haml @@ -1,7 +1,41 @@ -.pull-right.col-md-8#project_demographics - %h2 - = t('.demographics_by') - = link_to 'PAI', blog_url_for(:pai_about), target: '_blank' - = number_with_delimiter(@with_pai_count) - = t '.out_of', count: number_with_delimiter(@total_count) - #demographics_chart{ data: { src: demographic_chart_explores_path } } +.demographics-container#project_demographics + .demographics-header + %h2.section-title + = t('.demographics_by', default: 'Project Demographics by') + = link_to 'PAI', blog_url_for(:pai_about), target: '_blank', class: 'pai-link' + + %p.demographics-subtitle + - if defined?(@with_pai_count) && defined?(@total_count) + = "#{number_with_delimiter(@with_pai_count)} #{t('.projects_out_of', default: 'Projects out of')} #{number_with_delimiter(@total_count)} #{t('.total', default: 'total have a PAI available')}" + - else + = t('.default_stats', default: '85,813 Projects out of 255,787 total have a PAI available') + + / Demographics Chart + .demographics-chart-wrapper + #demographics_chart{ data: { src: demographic_chart_explores_path } } + + / Statistics Summary Cards + .statistics-summary + .stat-card.total-card + %p.stat-label= t('.total_projects', default: 'Total Projects') + %p.stat-value + - if defined?(@total_count) + = number_with_delimiter(@total_count) + - else + = '255,787' + + .stat-card.pai-card + %p.stat-label= t('.with_pai', default: 'With PAI') + %p.stat-value + - if defined?(@with_pai_count) + = number_with_delimiter(@with_pai_count) + - else + = '85,813' + + .stat-card.active-card + %p.stat-label= t('.active', default: 'Active') + %p.stat-value= '3.2%' + + .stat-card.new-card + %p.stat-label= t('.new', default: 'New') + %p.stat-value= '0.2%' diff --git a/app/views/explore/_projects.html.haml b/app/views/explore/_projects.html.haml index 90d50efc6..b635d120b 100644 --- a/app/views/explore/_projects.html.haml +++ b/app/views/explore/_projects.html.haml @@ -1,5 +1,41 @@ -= render 'projects_sidebar' +.explore-projects-page + / Full-width header above the sidebar+content grid (matches Figma layout) + .explore-page-wrapper + .explore-header + .explore-header-top + %h1.explore-title= t('.title', default: 'Projects') -= render 'hot_projects' + / Add New Project Button and Search Bar + .explore-header-actions + / Add New Project Button (hidden on mobile) + = link_to new_project_path, class: 'btn-add-project btn-desktop-only' do + %i.fa.fa-plus + = t('.add_new_project', default: 'Add New Project') -= render 'project_demographics' + / Search Bar with Filters (uses search-dingus styles) + = render 'shared/global_search', placeholder: t('.search_placeholder', default: 'Search projects...') + + / Mobile - Discover More Collapsible + .discover-more-mobile + %button.discover-toggle{ type: 'button', data: { toggle: 'collapse' } } + %span.discover-title= t('.discover_more', default: 'Discover More') + %span.chevron ▼ + + .discover-content.collapse + = render 'projects_sidebar' + + / Sidebar + Main Content Grid + .explore-main-container + / Left Sidebar - Desktop Only + %aside.explore-sidebar + = render 'projects_sidebar' + + / Main Content Area + %main.explore-content + / Hot Projects Section + .explore-section.hot-projects-section + = render 'hot_projects' + + / Project Demographics Section + .explore-section.demographics-section + = render 'project_demographics' diff --git a/app/views/explore/_projects_sidebar.html.haml b/app/views/explore/_projects_sidebar.html.haml index 21accd7b7..8b88904ae 100644 --- a/app/views/explore/_projects_sidebar.html.haml +++ b/app/views/explore/_projects_sidebar.html.haml @@ -1,44 +1,73 @@ -.well.col-md-3.margin_left_10.margin_top_20#explore_sidebar - = link_to t('.add_new_project'), new_project_path, class: 'btn btn-primary btn-mini add_new_project_btn_top' - %div - %h3= t('.search') - .margin_left_10 - %form.search#explore_search_form{ action: projects_path } - %input{ type: :hidden, name: 'ref', value: 'explore_project' } - %input.explore_search.margin_top_10{ type: :text, name: 'query', placeholder: t('.enter_string') } - .icon-search.search.submit - - %h3= t('.tags') - .margin_left_10{ style: 'min-height: 100px; padding-left: 15px' } - .row - #tagcloud +.sidebar-container + / Search Projects Section + .sidebar-card.search-card + .card-header + %h3.card-title= t('.search_projects', default: 'Search Projects') + + .card-body + %form.sidebar-search-form{ action: projects_path, role: 'search' } + .search-input-wrapper + = text_field_tag :query, params[:query], placeholder: t('.enter_search_string', default: 'Enter search string'), class: 'sidebar-search-input' + %button.search-icon-btn{ type: 'submit' } + %i.fa.fa-search + + / Explore Tags Section + .sidebar-card.tags-card + %button.card-header.collapsible{ type: 'button', data: { toggle: 'tags' } } + %h3.card-title= t('.explore_tags', default: 'Explore Tags') + + .card-body.tags-body + / Tag Cloud + .tag-cloud + - if defined?(@tags) && @tags.present? - @tags.each do |tag| - = link_to h(tag[0]), tags_path(names: tag[0]), data: { weight: tag[1] } - .margin_left_10 - %form.autocomplete-submit{ rel: 'tag_jump' } - %input.explore_search.tag_autocomplete.autocompletable#input_tag{ type: :text, placeholder: t('.enter_tag'), - data: { source: '/autocompletes/tags', select: 'submitForm' } } - - %p   - %h3= t('.compare') - %form.projects_compare.margin_left_15{ action: '/p/_compare' } - - compare_project_inputs.each do |options| - %input.proj.explore_compare{ options } - %input.pull-right.btn.btn-primary.btn-small#compare_btn{ type: 'submit', value: 'Compare' } - - %p   - %p   - %h3= t('.similar') - %form.autocomplete-submit.similar_projects.margin_left_10{ rel: 'similar_project_jump' } - %input.autocompletable.explore_search#project{ type: 'text', placeholder: t('.enter_project'), - autocomplete: 'off', data: { source: '/autocompletes/project', select: 'submitFormWithId' } } - .icon-search.search.submit - %span.error.hidden= t('.invalid_select') - - %p   - %h3= t('.add') - %p.justify.margin_left_10.margin_top_20{ style: 'line-height: 20px' } - = t('.community') - = t('.dont_see_a_project') - .text-center.add_new_project_btn_bottom - = link_to t('.add_new_project'), new_project_path, class: 'btn btn-primary btn-small' + = link_to h(tag[0]), tags_path(names: tag[0]), class: 'tag-item', data: { weight: tag[1] } + - else + - %w[3d C++ collection cross-platform development framework graphics linux php python web windows].each do |tag| + = link_to tag, tags_path(names: tag), class: 'tag-item' + + / Custom Tag Input with icon inside + .tag-input-wrapper + = text_field_tag :custom_tag, '', placeholder: t('.enter_different_tag', default: 'Enter different tag'), class: 'tag-input' + %button.tag-search-btn{ type: 'button' } + %i.fa.fa-search + + / Compare Section + .sidebar-card.compare-card + %button.card-header.collapsible{ type: 'button', data: { toggle: 'compare' } } + %h3.card-title= t('.compare', default: 'Compare:') + + .card-body.compare-body + %form.compare-form{ action: '/p/_compare', method: :get } + .compare-inputs + = text_field_tag 'project_0', '', placeholder: t('.enter_project_name', default: 'Enter a project name'), class: 'compare-input' + = text_field_tag 'project_1', '', placeholder: t('.enter_project_name', default: 'Enter a project name'), class: 'compare-input' + = text_field_tag 'project_2', '', placeholder: t('.enter_project_name', default: 'Enter a project name'), class: 'compare-input' + + %button.btn-compare{ type: 'submit' }= t('.compare_btn', default: 'Compare') + + / Similar Projects Section + .sidebar-card.similar-card + %button.card-header.collapsible{ type: 'button', data: { toggle: 'similar' } } + %h3.card-title= t('.similar_projects_to', default: 'Similar Projects to:') + + .card-body.similar-body + %form.similar-form.autocomplete-submit{ rel: 'similar_project_jump' } + .search-input-wrapper + = text_field_tag :project, '', placeholder: t('.enter_project_name', default: 'Enter a project name'), class: 'sidebar-search-input autocompletable', autocomplete: 'off', data: { source: '/autocompletes/project', select: 'submitFormWithId' } + %button.search-icon-btn{ type: 'button' } + %i.fa.fa-search + %span.error.hidden= t('.invalid_select', default: 'Invalid selection') + + / Add a Project Section + .sidebar-card.add-project-card + .card-header + %h3.card-title= t('.add_a_project', default: 'Add a Project:') + + .card-body + %p.add-project-text + = t('.community_driven', default: 'Open Hub is community driven web site that thrives when members tell us about new FOSS projects. If you don\'t see a project for which you are looking:') + + = link_to new_project_path, class: 'btn-add-project-full' do + %i.fa.fa-plus + = t('.add_new_project', default: 'Add New Project') diff --git a/app/views/explore/_stats_by_sector.html.haml b/app/views/explore/_stats_by_sector.html.haml index e62d9e6ac..0f209b6aa 100644 --- a/app/views/explore/_stats_by_sector.html.haml +++ b/app/views/explore/_stats_by_sector.html.haml @@ -4,17 +4,17 @@ avg_commits = @stats_by_sector.map(&:average_commits).max gauge_scale = scale_to(avg_commits, 1000) -.col-md-8.margin_left_10#stats-by-sector +#stats-by-sector %h2.margin_bottom_20= t '.title' %table.table{ style: 'border: 1px solid #ccc' } %thead - %tr{ style: 'background-color: #ccc' } + %tr %th.strong= t '.sector' %th.strong.center= t '.average' %th.strong.center= t '.orgs' %tbody - @stats_by_sector.each do |orgs| - %tr{ style: 'background-color: #fff' } + %tr %td.strong= Organization::ORG_TYPES.invert[orgs.org_type] %td #commit-gauge{ 'data-gauge' => orgs.average_commits.to_s, 'data-gauge-max' => gauge_scale.to_s, class: "gauge-#{orgs.org_type}" } diff --git a/app/views/explore/orgs.html.haml b/app/views/explore/orgs.html.haml index 03473713d..aefc10c8b 100644 --- a/app/views/explore/orgs.html.haml +++ b/app/views/explore/orgs.html.haml @@ -2,11 +2,15 @@ - content_for :html_title do = t('.page_title') -%h1.margin_bottom_20= t('.title') -.row - = render partial: 'most_active_orgs' - = render partial: 'orgs_by_30_day_commit_volume' -.margin_bottom_20 -.row - = render partial: 'newest_orgs' - = render partial: 'stats_by_sector' +.explore-page-wrapper + .explore-header-top + %h1= t('.title') + = render 'shared/search' + + .orgs-layout + .left-column + = render partial: 'most_active_orgs' + = render partial: 'newest_orgs' + .right-column + = render partial: 'orgs_by_30_day_commit_volume' + = render partial: 'stats_by_sector' diff --git a/app/views/explore/orgs_by_thirty_day_commit_volume.js.haml b/app/views/explore/orgs_by_thirty_day_commit_volume.js.haml index 518a3ffd7..092201bf0 100644 --- a/app/views/explore/orgs_by_thirty_day_commit_volume.js.haml +++ b/app/views/explore/orgs_by_thirty_day_commit_volume.js.haml @@ -1,12 +1,37 @@ - if @org_by_30_day_commits.empty? :plain $('#org_filter_no_result').removeClass('hidden'); - $('#30_day_commit_volume').addClass('hidden'); - $('#header').addClass('hidden'); + $('#orgs_by_30_days_volume .desktop-org-table').addClass('hidden'); + $('.mobile-org-container').addClass('hidden'); - else + - mobile_cards_html = capture do + - @org_by_30_day_commits.each do |org_30_day_activity| + - org = org_30_day_activity.organization + .org-card + .org-card-header + = link_to org.decorate.icon(:small), organization_path(org), title: org.name, class: 'org-card-icon' + = link_to h(truncate(org.name, length: 24)), organization_path(org), title: org.name, class: 'org-card-name' + %span.org-card-size= org_30_day_activity.decorate.project_count_text + .org-card-body + .org-card-row + .org-card-label= t '.type' + .org-card-value= org.org_type_label + .org-card-row + .org-card-label= t '.projects' + .org-card-value= org_30_day_activity.project_count + .org-card-row + .org-card-label= t '.affiliates' + .org-card-value= org_30_day_activity.affiliate_count + .org-card-row + .org-card-label= t '30_day_commits' + .org-card-value= org_30_day_activity.thirty_day_commit_count + :plain - $('#header').removeClass('hidden'); $('#org_filter_no_result').addClass('hidden'); - $('#30_day_commit_volume').removeClass('hidden'); + $('#orgs_by_30_days_volume .desktop-org-table').removeClass('hidden'); var partial = "#{ j render(partial: 'org_30_day_activity', collection: @org_by_30_day_commits) }" $('#30_day_commit_volume').html(partial); + + $('.mobile-org-container').removeClass('hidden'); + var mobileCards = "#{ j mobile_cards_html }"; + $('.card-body-mobile').html(mobileCards); diff --git a/app/views/explore/projects.html.haml b/app/views/explore/projects.html.haml index 9e84daa46..ed948b425 100644 --- a/app/views/explore/projects.html.haml +++ b/app/views/explore/projects.html.haml @@ -2,6 +2,4 @@ - content_for :html_title do = t('.page_title') -%h1.margin_top_15= t('.title') - = cache_projects_explore_page diff --git a/app/views/forums/_notice.html.haml b/app/views/forums/_notice.html.haml index eb28dfcfc..756339727 100644 --- a/app/views/forums/_notice.html.haml +++ b/app/views/forums/_notice.html.haml @@ -8,7 +8,7 @@ We’re excited to announce that we will be moving the Open Hub Forum to %a{href: "#{blog_link}"} #{blog_link}. Beginning immediately, users can head over, - %a{href: 'https://community.blackduck.com/s/topic/0TO2H000000gHS1WAM/black-duck-open-hub-help'} register, + %a{href: 'https://community.blackduck.com/s/topic/0TO2H000000gHS1WAM/black-duck-open-hub-help', target: '_blank', rel: 'noopener noreferrer'} register, get technical help and discuss issue pertinent to the Open Hub. Registered users can also subscribe to Open Hub announcements here. %p diff --git a/app/views/forums/show.html.haml b/app/views/forums/show.html.haml index 3653eb7e3..8f664e702 100644 --- a/app/views/forums/show.html.haml +++ b/app/views/forums/show.html.haml @@ -27,4 +27,4 @@ = link_to t('.add_new_topic'), new_forum_topic_path(@forum), class: 'btn btn-primary btn-mini disabled' .col-md-11{ style: 'margin-top: -20px' } - = will_paginate @topics + = render 'shared/modern_pagination', collection: @topics diff --git a/app/views/home/_compact_project_card.html.haml b/app/views/home/_compact_project_card.html.haml new file mode 100644 index 000000000..869421467 --- /dev/null +++ b/app/views/home/_compact_project_card.html.haml @@ -0,0 +1,39 @@ +- is_account = project.is_a?(Account) +- project_url = is_account ? account_path(project) : project_path(project) + +.compact_project_card{ data: { project_id: project.id } } + .compact_card_inner + .compact_card_header + .project_logo + = link_to project_url do + - if is_account + = image_tag(avatar_img_path(project), class: 'account-avatar', alt: h(project.name)) + - elsif project.logo.present? + = image_tag(project.logo.attachment.url(:small), alt: project.name, onerror: "this.style.display='none'; this.nextElementSibling.style.display='flex';") + .project-icon-placeholder{ style: 'display:none' } + = project.name[0].upcase + - else + .project-icon-placeholder + = project.name[0].upcase + .project_info + .project_name_row + = link_to project_url, class: 'project_name' do + %span= h(truncate(project.name, length: 30)) + - if !is_account && project.updated_at > 7.days.ago + %span.trending_icon + %i.fa.fa-arrow-trend-up + .compact_card_description + - description = is_account ? (project.try(:about) || '') : (project.description || '') + %p= h(truncate(description, length: 80)) + .compact_card_footer + .footer_left + .contributor_info + %i.fa.fa-users + - count = is_account ? (project.best_account_analysis&.account_analysis_fact&.thirty_day_commits || 0) : (project.best_analysis&.headcount || 0) + %span= count + .footer_right + - if is_account + - language_name = project.best_account_analysis&.account_analysis_fact&.primary_language&.nice_name || 'N/A' + - else + - language_name = project.best_analysis&.main_language&.nice_name || 'NA' + %span.language_badge= language_name diff --git a/app/views/home/_join_now_home.html.haml b/app/views/home/_join_now_home.html.haml index 2fe5d5a3c..b2a24f233 100644 --- a/app/views/home/_join_now_home.html.haml +++ b/app/views/home/_join_now_home.html.haml @@ -1,16 +1,28 @@ -.join_now_home - %ul - %li.fa.fa-stop.fa-rotate-45.join_now_color - %span.join_now_color= t('.claim') - %span.join_now_color= t('.contributions') - %p - %li.fa.fa-stop.fa-rotate-45.join_now_color - %span.join_now_color= t('.manage') - %span.join_now_color= t('.projects_data') - %p - %li.fa.fa-stop.fa-rotate-45.join_now_color - %span.join_now_color= t('.highlight') - %span.join_now_color= t('.use') - %br - %p - = link_to t('.join_now'), h(new_account_path(invite: params[:invite], ref: 'homepage')), class: 'btn btn_join_now' +.join_now_card + .card_inner + .join_header + .join_icon + %i.fa.fa-user-plus + .join_header_text + %h3.join_title= t('.join_now') + %p.join_subtitle= t('.become_part_community') + + .benefits_list + .benefit_item + .benefit_icon.benefit_icon_award + %i.fa.fa-trophy + .benefit_content + %h4.benefit_title= t('.track_contributions_title') + %p.benefit_description= t('.track_contributions_desc') + + .benefit_item + .benefit_icon.benefit_icon_trending + %i.fa.fa-line-chart + .benefit_content + %h4.benefit_title= t('.gain_recognition_title') + %p.benefit_description= t('.gain_recognition_desc') + + .join_cta + = link_to new_account_path(invite: params[:invite], ref: 'homepage'), class: 'btn btn_join_cta' do + %i.fa.fa-user-plus + = t('.create_account') diff --git a/app/views/home/_top_contributors.html.haml b/app/views/home/_top_contributors.html.haml new file mode 100644 index 000000000..9faa51eae --- /dev/null +++ b/app/views/home/_top_contributors.html.haml @@ -0,0 +1,34 @@ +.top_contributors_card + .card_header + .header_icon + %i.fa.fa-users + .header_content + %h3.card_title= t('.top_contributors') + %p.card_subtitle= t('.most_active_developers') + + .contributors_list + - @home.most_active_contributors&.each_with_index do |contributor, index| + = link_to account_path(contributor), class: 'contributor_item' do + .contributor_avatar + - begin + = image_tag avatar_img_path(contributor, 40), class: 'avatar_img', alt: contributor.name, onerror: "this.onerror=null; this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2240%22 height=%2240%22 viewBox=%220 0 40 40%22%3E%3Crect width=%2240%22 height=%2240%22 rx=%226%22 fill=%22%239CA3AF%22/%3E%3Ctext x=%2250%25%22 y=%2250%25%22 dominant-baseline=%22middle%22 text-anchor=%22middle%22 font-family=%22Arial%22 font-size=%2218%22 font-weight=%22600%22 fill=%22white%22%3E#{h(contributor.name[0].upcase)}%3C/text%3E%3C/svg%3E';" + - rescue + %div.avatar_placeholder{style: "width: 40px; height: 40px; border-radius: 12px; background: #9CA3AF; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; font-size: 18px;"} + = contributor.name[0].upcase + + .contributor_info + .contributor_name_row + %span.contributor_name= h(contributor.name) + .contributor_stats + - commits = contributor.best_account_analysis&.account_analysis_fact&.thirty_day_commits || 0 + %span.stat_commits + %i.fa.fa-dot-circle-o + = "#{number_with_delimiter(commits)} commits" + + .contributor_arrow + %i.fa.fa-chevron-right + + .view_all_footer + = link_to accounts_path, class: 'view_all_link' do + %i.fa.fa-users + %span= t('.view_all_contributors') diff --git a/app/views/home/_top_lists.html.haml b/app/views/home/_top_lists.html.haml index 6630636ee..02db80a5a 100644 --- a/app/views/home/_top_lists.html.haml +++ b/app/views/home/_top_lists.html.haml @@ -1,56 +1,85 @@ -.col-md-3.top_ten - - if device? - %h2.most_popular_projects - %button.collapsed#no-background{"aria-controls" => "collapseOne", "aria-expanded" => "false", "data-target" => "#collapseOne", "data-toggle" => "collapse"} - %span.toggle-icon.glyphicon.glyphicon-chevron-down.pull-right#collpase1 - =t('.popular_projects') - .collapse#collapseOne - = render partial: 'content', locals: { popular_items: @home.most_popular_projects, - commit: 'user', colors: 'popular', required: 'most_popular_projects', - max: @home.most_popular_projects.map(&:user_count).max } - - else - %h2.most_popular_projects= t('.popular_projects') - = render partial: 'content', locals: { popular_items: @home.most_popular_projects, - commit: 'user', colors: 'popular', required: 'most_popular_projects', - max: @home.most_popular_projects.map(&:user_count).max } -.col-md-3.top_ten.middle - - if device? - %h2.most_active_projects - %button.collapsed{"aria-controls" => "collapseTwo", "aria-expanded" => "false", "data-target" => "#collapseTwo", "data-toggle" => "collapse"} - %span.toggle-icon.glyphicon.glyphicon-chevron-down.pull-right#collpase2 - =t('.active_projects') - .collapse#collapseTwo - = render partial: 'content', locals: { popular_items: @home.most_active_projects, - commit: 'commit', colors: 'active', required: 'most_active_projects', max: @home.commit_count.max } - - else - %h2.most_active_projects= t('.active_projects') - = render partial: 'content', locals: { popular_items: @home.most_active_projects, - commit: 'commit', colors: 'active', required: 'most_active_projects', max: @home.commit_count.max } -.col-md-3.top_ten.last - - if device? - %h2.most_recent_projects - %button.collapsed#no-background{"aria-controls" => "collapseThree", "aria-expanded" => "false", "data-target" => "#collapseThree", "data-toggle" => "collapse"} - %span.toggle-icon.glyphicon.glyphicon-chevron-down.pull-right#collpase3 - =t('.recent_projects') - .collapse#collapseThree - = render partial: 'content', locals: { popular_items: @home.most_recent_projects, - commit: 'since', colors: 'recent', required: 'most_recent_projects', - max: @home.most_recent_projects.size } - - else - %h2.most_recent_projects=t('.recent_projects') - = render partial: 'content', locals: { popular_items: @home.most_recent_projects, - commit: 'since', colors: 'recent', required: 'most_recent_projects', - max: @home.most_recent_projects.size } -.col-md-3.top_ten.middle - - if device? - %h2.most_active_contributors - %button.collapsed{"aria-controls" => "collapseFour", "aria-expanded" => "false", "data-target" => "#collapseFour", "data-toggle" => "collapse"} - %span.toggle-icon.glyphicon.glyphicon-chevron-down.pull-right#collpase4 - =t('.active_contributors') - .collapse#collapseFour - = render partial: 'content', locals: { popular_items: @home.most_active_contributors, - commit: 'commit', colors: 'contributor', required: 'most_active_contributors', max: @home.account_analysis_count.max } - - else - %h2.most_active_contributors=t('.active_contributors') - = render partial: 'content', locals: { popular_items: @home.most_active_contributors, - commit: 'commit', colors: 'contributor', required: 'most_active_contributors', max: @home.account_analysis_count.max } \ No newline at end of file +/ Project Swimlanes Section - New Design +.project_swimlanes_section + .swimlanes_container + .swimlanes_grid + / Most Active Projects Column + .swimlane_column + .swimlane_header + %h2.swimlane_title= t('.active_projects') + .swimlane_content{ data: { swimlane: 'active' } } + / Desktop: Show all projects + - @home.most_active_projects.each_with_index do |project, index| + .desktop_only + = render partial: 'compact_project_card', locals: { project: project } + + / Mobile: Show first 2 projects initially + - @home.most_active_projects.first(2).each do |project| + .mobile_only.mobile_card + = render partial: 'compact_project_card', locals: { project: project } + + / Mobile: Hidden cards (to be shown on click) + - if @home.most_active_projects.size > 2 + - @home.most_active_projects[2..9].each do |project| + .mobile_only.mobile_card.hidden_card{ style: 'display: none;' } + = render partial: 'compact_project_card', locals: { project: project } + + / Show More Button (mobile only) + %button.show_more_btn.mobile_only{ type: 'button', data: { swimlane: 'active', target: 'active', expanded: 'false', show_text: t('.show_more'), hide_text: t('.show_less') } } + = t('.show_more') + + / Most Popular Projects Column + .swimlane_column + .swimlane_header + %h2.swimlane_title= t('.popular_projects') + .swimlane_content{ data: { swimlane: 'popular' } } + / Desktop: Show all projects + - @home.most_popular_projects.each_with_index do |project, index| + .desktop_only + = render partial: 'compact_project_card', locals: { project: project } + + / Mobile: Show first 2 projects initially + - @home.most_popular_projects.first(2).each do |project| + .mobile_only.mobile_card + = render partial: 'compact_project_card', locals: { project: project } + + / Mobile: Hidden cards (to be shown on click) + - if @home.most_popular_projects.size > 2 + - @home.most_popular_projects[2..9].each do |project| + .mobile_only.mobile_card.hidden_card{ style: 'display: none;' } + = render partial: 'compact_project_card', locals: { project: project } + + / Show More Button (mobile only) + %button.show_more_btn.mobile_only{ type: 'button', data: { swimlane: 'popular', target: 'popular', expanded: 'false', show_text: t('.show_more'), hide_text: t('.show_less') } } + = t('.show_more') + + / Recently Added Projects Column + .swimlane_column + .swimlane_header + %h2.swimlane_title= t('.recent_projects') + .swimlane_content{ data: { swimlane: 'recent' } } + / Desktop: Show all projects + - @home.most_recent_projects.each_with_index do |project, index| + .desktop_only + = render partial: 'compact_project_card', locals: { project: project } + + / Mobile: Show first 2 projects initially + - @home.most_recent_projects.first(2).each do |project| + .mobile_only.mobile_card + = render partial: 'compact_project_card', locals: { project: project } + + / Mobile: Hidden cards (to be shown on click) + - if @home.most_recent_projects.size > 2 + - @home.most_recent_projects[2..9].each do |project| + .mobile_only.mobile_card.hidden_card{ style: 'display: none;' } + = render partial: 'compact_project_card', locals: { project: project } + + / Show More Button (mobile only) + %button.show_more_btn.mobile_only{ type: 'button', data: { swimlane: 'recent', target: 'recent', expanded: 'false', show_text: t('.show_more'), hide_text: t('.show_less') } } + = t('.show_more') + + + / View All Projects Button + .view_all_container + = link_to projects_path, class: 'btn_view_all_projects' do + = t('.view_all_projects') + %i.fa.fa-arrow-right diff --git a/app/views/home/_user_journeys.html.haml b/app/views/home/_user_journeys.html.haml new file mode 100644 index 000000000..a5d6cc56e --- /dev/null +++ b/app/views/home/_user_journeys.html.haml @@ -0,0 +1,272 @@ +.user_journeys_section + .container + .journeys_header + %h2.journeys_title= t('.your_journey_with_openhub') + %p.journeys_subtitle= t('.explore_ways_description') + + / Mobile View - Collapsible Cards + .journeys_mobile + .journey_card + %button.journey_card_header.journey_toggle{'data-journey-index': '0'} + .journey_header_icon.journey_icon_compare + %i.fa.fa-balance-scale + %h3.journey_header_title= t('.discover_compare_title') + .journey_toggle_icon + %i.fa.fa-plus + .journey_card_content + %p.journey_description= t('.discover_compare_desc') + .journey_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.discover_compare_step1') + %li + %span.step_bullet • + %span= t('.discover_compare_step2') + %li + %span.step_bullet • + %span= t('.discover_compare_step3') + + .journey_card + %button.journey_card_header.journey_toggle{'data-journey-index': '1'} + .journey_header_icon.journey_icon_health + %i.fa.fa-heartbeat + %h3.journey_header_title= t('.assess_health_title') + .journey_toggle_icon + %i.fa.fa-plus + .journey_card_content + %p.journey_description= t('.assess_health_desc') + .journey_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.assess_health_step1') + %li + %span.step_bullet • + %span= t('.assess_health_step2') + %li + %span.step_bullet • + %span= t('.assess_health_step3') + + .journey_card + %button.journey_card_header.journey_toggle{'data-journey-index': '2'} + .journey_header_icon.journey_icon_portfolio + %i.fa.fa-id-card + %h3.journey_header_title= t('.manage_portfolio_title') + .journey_toggle_icon + %i.fa.fa-plus + .journey_card_content + %p.journey_description= t('.manage_portfolio_desc') + .journey_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.manage_portfolio_step1') + %li + %span.step_bullet • + %span= t('.manage_portfolio_step2') + %li + %span.step_bullet • + %span= t('.manage_portfolio_step3') + + .journey_card + %button.journey_card_header.journey_toggle{'data-journey-index': '3'} + .journey_header_icon.journey_icon_analyze + %i.fa.fa-bar-chart + %h3.journey_header_title= t('.analyze_code_title') + .journey_toggle_icon + %i.fa.fa-plus + .journey_card_content + %p.journey_description= t('.analyze_code_desc') + .journey_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.analyze_code_step1') + %li + %span.step_bullet • + %span= t('.analyze_code_step2') + %li + %span.step_bullet • + %span= t('.analyze_code_step3') + + .journey_card + %button.journey_card_header.journey_toggle{'data-journey-index': '4'} + .journey_header_icon.journey_icon_security + %i.fa.fa-shield + %h3.journey_header_title= t('.check_security_title') + .journey_toggle_icon + %i.fa.fa-plus + .journey_card_content + %p.journey_description= t('.check_security_desc') + .journey_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.check_security_step1') + %li + %span.step_bullet • + %span= t('.check_security_step2') + %li + %span.step_bullet • + %span= t('.check_security_step3') + + .journey_card + %button.journey_card_header.journey_toggle{'data-journey-index': '5'} + .journey_header_icon.journey_icon_contribute + %i.fa.fa-search + %h3.journey_header_title= t('.identify_opportunities_title') + .journey_toggle_icon + %i.fa.fa-plus + .journey_card_content + %p.journey_description= t('.identify_opportunities_desc') + .journey_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.identify_opportunities_step1') + %li + %span.step_bullet • + %span= t('.identify_opportunities_step2') + %li + %span.step_bullet • + %span= t('.identify_opportunities_step3') + + / Desktop View - Grid Cards + .journeys_desktop + .journey_grid_card + .grid_card_icon.journey_icon_compare + %i.fa.fa-balance-scale + %h3.grid_card_title= t('.discover_compare_title') + %p.grid_card_description= t('.discover_compare_desc') + .grid_card_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.discover_compare_step1') + %li + %span.step_bullet • + %span= t('.discover_compare_step2') + %li + %span.step_bullet • + %span= t('.discover_compare_step3') + + .journey_grid_card + .grid_card_icon.journey_icon_health + %i.fa.fa-heartbeat + %h3.grid_card_title= t('.assess_health_title') + %p.grid_card_description= t('.assess_health_desc') + .grid_card_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.assess_health_step1') + %li + %span.step_bullet • + %span= t('.assess_health_step2') + %li + %span.step_bullet • + %span= t('.assess_health_step3') + + .journey_grid_card + .grid_card_icon.journey_icon_portfolio + %i.fa.fa-id-card + %h3.grid_card_title= t('.manage_portfolio_title') + %p.grid_card_description= t('.manage_portfolio_desc') + .grid_card_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.manage_portfolio_step1') + %li + %span.step_bullet • + %span= t('.manage_portfolio_step2') + %li + %span.step_bullet • + %span= t('.manage_portfolio_step3') + + .journey_grid_card + .grid_card_icon.journey_icon_analyze + %i.fa.fa-bar-chart + %h3.grid_card_title= t('.analyze_code_title') + %p.grid_card_description= t('.analyze_code_desc') + .grid_card_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.analyze_code_step1') + %li + %span.step_bullet • + %span= t('.analyze_code_step2') + %li + %span.step_bullet • + %span= t('.analyze_code_step3') + + .journey_grid_card + .grid_card_icon.journey_icon_security + %i.fa.fa-shield + %h3.grid_card_title= t('.check_security_title') + %p.grid_card_description= t('.check_security_desc') + .grid_card_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.check_security_step1') + %li + %span.step_bullet • + %span= t('.check_security_step2') + %li + %span.step_bullet • + %span= t('.check_security_step3') + + .journey_grid_card + .grid_card_icon.journey_icon_contribute + %i.fa.fa-search + %h3.grid_card_title= t('.identify_opportunities_title') + %p.grid_card_description= t('.identify_opportunities_desc') + .grid_card_steps + %h4.steps_title= t('.key_steps') + %ul.steps_list + %li + %span.step_bullet • + %span= t('.identify_opportunities_step1') + %li + %span.step_bullet • + %span= t('.identify_opportunities_step2') + %li + %span.step_bullet • + %span= t('.identify_opportunities_step3') + +:javascript + document.addEventListener('DOMContentLoaded', function() { + const toggleButtons = document.querySelectorAll('.journey_toggle'); + + toggleButtons.forEach(function(button) { + button.addEventListener('click', function() { + const journeyCard = this.closest('.journey_card'); + const content = journeyCard.querySelector('.journey_card_content'); + const icon = this.querySelector('.journey_toggle_icon i'); + + journeyCard.classList.toggle('expanded'); + + if (journeyCard.classList.contains('expanded')) { + icon.classList.remove('fa-plus'); + icon.classList.add('fa-minus'); + } else { + icon.classList.remove('fa-minus'); + icon.classList.add('fa-plus'); + } + }); + }); + }); diff --git a/app/views/home/_whats_new.html.haml b/app/views/home/_whats_new.html.haml index 7e7b6eeaa..f77305e6e 100644 --- a/app/views/home/_whats_new.html.haml +++ b/app/views/home/_whats_new.html.haml @@ -1,2 +1,9 @@ -%a{ href: 'https://www.blackduck.com/resources/analyst-reports/open-source-security-risk-analysis.html', target: '_blank', class: 'whats-new-banner' } - %img{src: image_path('home/OSSRA2026-OH-banner.png'), alt: 'Open Source Security and Risk Analysis'} +.whats_new_card + .card_inner + .card_header_section + %h3.whats_new_title= t('.whats_new') + %p.whats_new_subtitle= t('.latest_updates_reports') + + .featured_image_section + %a{ href: 'https://www.blackduck.com/resources/analyst-reports/open-source-security-risk-analysis.html', target: '_blank', rel: 'noopener noreferrer', class: 'featured_image_link' } + %img.featured_image{src: image_path('home/OSSRA2026-OH-banner.png'), alt: t('.ossra_report_alt')} diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index c47b3002b..523849b8c 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -1,31 +1,84 @@ - content_for(:html_title) { t('.page_title') } -.showcase - .billboard - .row - .col-md-12 - %h2.discover_msg - = t('.discover_message') - .row - .col-md-12.billboard_search - %form.search#search_form{ action: projects_path } - %input{ type: :hidden, name: 'ref', value: 'homepage' } - %input.for_search_all_code{ type: :text, name: 'query', placeholder: t('.search_projects'), id: 'text' } - %i#icon_text.fa.fa-search - - .row - .col-md-12 - .global_stats - #global_statistics - %p.hide#content-0= t('.counting', count: number_with_delimiter(@home.lines_count.to_i)) - %p.hide#content-1= t('.indexing', count: number_with_delimiter(@home.active_project_count)) - %p.hide#content-2= t('.connecting', count: number_with_delimiter(@home.person_count)) - %p.hide#content-3= t('.tracking', count: number_with_delimiter(@home.repository_count)) -.home_page_row - .col-md-4.col-xs-7.col-sm-6 - %h2.join_now= t('.join_now') - = render partial: '/home/join_now_home' - .col-md-8.col-xs-5.col-sm-6 - %h2.wh_new= t('.whats_new') - = render partial: '/home/whats_new' -.landing.col-md-12.col-sm-12.col-xs-12.last_section - = home_top_lists +- content_for(:javascript) do + = javascript_include_tag 'project_swimlanes' + +/ Hero Section +.hero_section + .hero_container + / Hero Header - Centered + .hero_header + %h1.hero_title= t('.discover_message') + %p.hero_subtitle= t('.worlds_largest_directory') + + / Search Bar - Centered & Compact + .search_container + %form.search_form#search_form{ action: projects_path } + %input{ type: :hidden, name: 'ref', value: 'homepage' } + .search_input_wrapper + %label.sr-only{ for: 'text' }= t('.search_projects') + .search_icon_wrapper + %i.fa.fa-search.search_icon + %input.search_input{ type: :text, name: 'query', placeholder: t('.search_projects'), id: 'text', autocomplete: 'off' } + + / Rotating Stats + .rotating_stats + #global_statistics + %p.hide#content-0= t('.counting', count: number_with_delimiter(@home.lines_count.to_i)) + %p.hide#content-1= t('.indexing', count: number_with_delimiter(@home.active_project_count)) + %p.hide#content-2= t('.connecting', count: number_with_delimiter(@home.person_count)) + %p.hide#content-3= t('.tracking', count: number_with_delimiter(@home.repository_count)) + + / Feature Cards - Moved to Top + .features_section + .feature_card + .feature_icon.feature_icon_yellow + %i.fa.fa-database + %h3.feature_title= t('.feature_directory_title') + %p.feature_description= t('.feature_directory_desc') + .feature_card + .feature_icon.feature_icon_blue + %i.fa.fa-heartbeat + %h3.feature_title= t('.feature_health_title') + %p.feature_description= t('.feature_health_desc') + .feature_card + .feature_icon.feature_icon_green + %i.fa.fa-user-circle + %h3.feature_title= t('.feature_portfolio_title') + %p.feature_description= t('.feature_portfolio_desc') + + / Quick Stats Bar - Ultra Compact & Elegant + .quick_stats_bar + .stat_item + .stat_value= "2.5M+" + .stat_label= t('.projects_label') + .stat_divider + .stat_item + .stat_value= number_with_delimiter(@home.person_count / 1000) + "K" + .stat_label= t('.contributors_label') + .stat_divider + .stat_item + .stat_value= "450+" + .stat_label= t('.languages_label') + .stat_divider + .stat_item + .stat_value= "12K" + .stat_label= t('.organizations_label') +/ Top Contributors and What's New Section +.content_section + .container + .content_grid + / Top Contributors - 35% + .contributors_column + = render partial: '/home/top_contributors' + + / What's New and Join Now - 65% + .whats_new_column + .whats_new_join_stack + = render partial: '/home/whats_new' + = render partial: '/home/join_now_home' + +/ Project Swimlanes Section += home_top_lists + +/ User Journeys Section += render partial: '/home/user_journeys' diff --git a/app/views/invites/new.html.haml b/app/views/invites/new.html.haml index 5834fe3af..3e33d680e 100644 --- a/app/views/invites/new.html.haml +++ b/app/views/invites/new.html.haml @@ -1,7 +1,7 @@ - content_for(:html_title) { t('.page_title') } %h1.float_left.margin_bottom_15 Invite User -.col-md-11.well +.col-md-11.oh-card = form_for(@invite, url: contributor_invites_path(@contribution), html: { method: :post }) do |f| %fieldset %legend= t('.invite_to_join', email: h(obfuscate_email(@invite.name.name))) diff --git a/app/views/kudos/_given.html.haml b/app/views/kudos/_given.html.haml index 478533f48..b999db2c6 100644 --- a/app/views/kudos/_given.html.haml +++ b/app/views/kudos/_given.html.haml @@ -3,29 +3,24 @@ first_kudo = kudo_array.delete_at(0) other_kudos = kudo_array -%table.table.table-condensed.unstyled.nomargin - %tbody - %tr - %td{ width: '23', style: 'vertical-align: text-top !important;' } - = avatar_tiny_laurels(kudo_rank_from_kudo(first_kudo)) - %td{ id: (first_kudo.account.nil? ? '' : "kudo_given_#{first_kudo.account.login}") } - = kudo_person_link(first_kudo) - %span{ style: 'margin-left: 50px;' } - - kudo_delete_link(first_kudo) if logged_in? - - if kudo_is_new?(first_kudo.account_id, first_kudo.created_at) - %span.signature_color{ style: 'margin-left: 20px;' }= t('kudos.new_link') - - if first_kudo.project_id - .project= t('kudos.for', what: first_kudo.project.name) - - unless first_kudo.message.blank? - .message “#{first_kudo.message}” - %table{ id: (first_kudo.account.nil? ? '' : "kudo_given_alternates_#{first_kudo.account.login}") } - %tbody - - other_kudos.each do |kudo| - %tr - %td - = t('.aka') - = kudos_aka_name(kudo) - - unless kudo.message.blank? - .message “#{kudo.message}” - %td - - kudo_delete_link(kudo) if logged_in? +.kudos-item{ id: (first_kudo.account.nil? ? '' : "kudo_given_#{first_kudo.account.login}") } + .kudos-item__avatar + = avatar_tiny_laurels(kudo_rank_from_kudo(first_kudo)) + .kudos-item__body + .kudos-item__name-row + %span.kudos-item__name= kudo_person_link(first_kudo) + - if kudo_is_new?(first_kudo.account_id, first_kudo.created_at) + %span.kudos-item__new= t('kudos.new_link') + - kudo_delete_link(first_kudo) if logged_in? + - if first_kudo.project_id + .kudos-item__project= t('kudos.for', what: first_kudo.project.name) + - unless first_kudo.message.blank? + .kudos-item__message “#{first_kudo.message}” + - if other_kudos.any? + .kudos-item__aka{ id: (first_kudo.account.nil? ? '' : "kudo_given_alternates_#{first_kudo.account.login}") } + - other_kudos.each do |kudo| + %span= t('.aka') + = kudos_aka_name(kudo) + - unless kudo.message.blank? + .kudos-item__message “#{kudo.message}” + - kudo_delete_link(kudo) if logged_in? diff --git a/app/views/kudos/_received.html.haml b/app/views/kudos/_received.html.haml index 9d84b3267..0535c2234 100644 --- a/app/views/kudos/_received.html.haml +++ b/app/views/kudos/_received.html.haml @@ -1,16 +1,14 @@ - kudo = received -%table.table.table-condensed.unstyled.nomargin - %tbody - %tr - %td{ width: '23', style: 'vertical-align: text-top !important;' } - = avatar_tiny_laurels(kudo.sender.kudo_rank) - %td{ id: "kudo_received_#{kudo.sender.login}" } - = link_to h(kudo.sender.name), account_url(kudo.sender), id: "kudo_received_link_#{kudo.sender.login}" - - if kudo_is_new?(kudo.account_id, kudo.created_at) - %span.signature_color{ style: 'margin-left: 20px;' }= t('kudos.new_link') - %span{ style: 'margin-left: 50px;' } - - kudo_delete_link(kudo) if logged_in? - - if kudo.project_id - .project= t('kudos.for', what: kudo.project.name) - - unless kudo.message.blank? - .message “#{kudo.message}” +.kudos-item{ id: "kudo_received_#{kudo.sender.login}" } + .kudos-item__avatar + = avatar_tiny_laurels(kudo.sender.kudo_rank) + .kudos-item__body + .kudos-item__name-row + = link_to h(kudo.sender.name), account_url(kudo.sender), id: "kudo_received_link_#{kudo.sender.login}", class: 'kudos-item__name' + - if kudo_is_new?(kudo.account_id, kudo.created_at) + %span.kudos-item__new= t('kudos.new_link') + - kudo_delete_link(kudo) if logged_in? + - if kudo.project_id + .kudos-item__project= t('kudos.for', what: kudo.project.name) + - unless kudo.message.blank? + .kudos-item__message “#{kudo.message}” diff --git a/app/views/kudos/index.html.haml b/app/views/kudos/index.html.haml index 8648583e1..0d2802d0e 100644 --- a/app/views/kudos/index.html.haml +++ b/app/views/kudos/index.html.haml @@ -3,7 +3,8 @@ page_context[:select_footer_nav] = :kudos position = @person ? kudo_position_for_person(@person.id, @person.kudo_position.to_i) : kudo_position_for_person(nil) .col-md-12 - %h2= t('.title') + %h2 + %strong{ style: 'font-size: 26px' }= t('.title') != position %table#kudo_mantle %tr @@ -19,15 +20,30 @@ .arrow_down   .row   .row   -.col-md-2.no-margin -   -.col-md-5 - %h5= t('.kudos_received') - = render partial: 'kudos/received', collection: @received_kudos - - if @received_kudos.empty? - %p= t('.no_kudos_received_yet') -.col-md-5 - %h5= t('.kudos_given') - = render partial: 'kudos/given', collection: kudos_grouped_sent(@sent_kudos) - - if @sent_kudos.empty? - %p= t('.no_kudos_given_yet') +.col-md-12 + .kudos-grid + .oh-card.oh-card--flush.kudos-card + .kudos-card__header + .kudos-card__icon.kudos-card__icon--received + %svg{ xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' } + %path{ d: 'M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3z' } + %path{ d: 'M7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3' } + %h2.kudos-card__title= t('.kudos_received') + .kudos-card__list + = render partial: 'kudos/received', collection: @received_kudos + - if @received_kudos.empty? + %p.kudos-card__empty= t('.no_kudos_received_yet') + .oh-card.oh-card--flush.kudos-card + .kudos-card__header + .kudos-card__icon.kudos-card__icon--given + %svg{ xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' } + %polyline{ points: '20 12 20 22 4 22 4 12' } + %rect{ x: '2', y: '7', width: '20', height: '5' } + %path{ d: 'M12 22V7' } + %path{ d: 'M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z' } + %path{ d: 'M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z' } + %h2.kudos-card__title= t('.kudos_given') + .kudos-card__list + = render partial: 'kudos/given', collection: kudos_grouped_sent(@sent_kudos) + - if @sent_kudos.empty? + %p.kudos-card__empty= t('.no_kudos_given_yet') diff --git a/app/views/kudos/new.html.haml b/app/views/kudos/new.html.haml index a828dd490..df2110d4e 100644 --- a/app/views/kudos/new.html.haml +++ b/app/views/kudos/new.html.haml @@ -1,20 +1,45 @@ -.row{ style: 'margin-top: 1em;' } - .col-md-11 - .well - = form_for @kudo, as: :kudo, url: kudos_path, html: { method: :post } do |f| - %fieldset - .control-group - %label.control-label= t('.include_message', who: @kudo.person_name) - .controls - = f.text_field :message, size: 80 - - error_tag @kudo, :message - %p.help-block= t('.maximum') - - - if @kudo.account - = f.hidden_field :account_id, value: @kudo.account_id - - else - = f.hidden_field :contribution_id, value: @contribution.id - = f.hidden_field :name_id, value: @kudo.name_id - - .actions - %input.btn.btn-mini.btn-primary{ type: 'submit', value: t('.submit') } +.kudo-form + = form_for @kudo, as: :kudo, url: kudos_path, html: { method: :post } do |f| + .kudo-modal-header + .kudo-modal-icon-wrap + %svg{ xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' } + %path{ d: 'M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3z' } + %path{ d: 'M7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3' } + .kudo-modal-header-text + %h2= t('kudos.give_kudos_to', name: @kudo.person_name) + %p= t('.show_appreciation') + .kudo-modal-body + .kudo-modal-card + %label.kudo-message-label + %span= t('.include_message', who: @kudo.person_name) + = f.text_area :message, maxlength: 80, rows: 3, placeholder: t('.message_placeholder'), class: 'kudo-textarea', id: 'kudo_message_ta' + - error_tag @kudo, :message + .kudo-char-counter + %span= t('.maximum') + %span.kudo-count + %span#kudo_char_num 0 + | / 80 + - if @kudo.account + = f.hidden_field :account_id, value: @kudo.account_id + - else + = f.hidden_field :contribution_id, value: @contribution.id + = f.hidden_field :name_id, value: @kudo.name_id + .kudo-modal-footer + %button.kudo-btn-cancel{ type: 'button', onclick: 'tb_remove(); return false;' }= t('cancel') + %button.kudo-btn-submit{ type: 'submit' } + %svg.kudo-submit-icon{ xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' } + %path{ d: 'M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3z' } + %path{ d: 'M7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3' } + = t('.submit') + :javascript + (function() { + var win = document.getElementById('TB_window'); + if (win) win.classList.add('kudo-active'); + var ta = document.getElementById('kudo_message_ta'); + var num = document.getElementById('kudo_char_num'); + if (ta && num) { + var updateCount = function() { num.textContent = ta.value.length; }; + updateCount(); + ta.addEventListener('input', updateCount); + } + })(); diff --git a/app/views/languages/_language.html.haml b/app/views/languages/_language.html.haml index 8e5345417..6ba14b52a 100644 --- a/app/views/languages/_language.html.haml +++ b/app/views/languages/_language.html.haml @@ -1,6 +1,6 @@ - show_lang_usage ||= false -.well +.oh-card %h4.pull-left = link_to language.nice_name, language_path(language) diff --git a/app/views/languages/compare.html.haml b/app/views/languages/compare.html.haml index 1629ac9c0..e10448afc 100644 --- a/app/views/languages/compare.html.haml +++ b/app/views/languages/compare.html.haml @@ -1,34 +1,56 @@ - content_for(:html_title) { t('.title') } -%h3= t('.heading') +.languages-compare-container + .languages-header + %h1.languages-title= t('.heading') -- measures.each do |key, value| - = link_to h(value), compare_languages_path(measure: key, language_name: params[:language_name]), - class: "btn btn-mini #{@measure == key ? 'btn-primary' : 'btn-info'}" + .languages-measures-card + .measure-buttons-group + - measures.each do |key, value| + = link_to h(value), compare_languages_path(measure: key, language_name: params[:language_name]), + class: "measure-btn #{@measure == key ? 'active' : ''}" -%h5 - = t('.measure_heading', measure: measures[@measure]) -%p - = measure_description[@measure] - = link_to t('.more'), 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Language-Comparison-Page', target: '_blank' + .languages-info-card + %h2.section-heading= t('.measure_heading', measure: measures[@measure]) + %p.info-text + = measure_description[@measure] + = link_to t('.more'), 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Language-Comparison-Page', target: '_blank', class: 'info-link' -= form_tag(compare_languages_path(measure: @measure), method: :get) do - = hidden_field_tag :measure, @measure - .col-md-8 - .chart.watermark440#language{ datasrc: chart_languages_path(measure: @measure, language_name: @language_names), - style: 'height: 270px;width: 480px' } - .col-md-4 - - @language_names.each do |name| - .col-md-3{ style: "height: 28px;background-color: ##{language_color(name)}" } -   - .col-md-9.margin_bottom_10 - #sort_by - = select_tag 'language_name[]', options_for_select(@languages, name), class: 'chzn-select col-md-9' - .clearfix - .language - .col-md-3   - .col-md-9.margin_bottom_10.last_language - #sort_by - = select_tag 'language_name[]', options_for_select(@languages), class: 'chzn-select col-md-9' + .languages-content-card + = form_tag(compare_languages_path(measure: @measure), method: :get) do + = hidden_field_tag :measure, @measure + .content-grid + .chart-section + .chart.watermark440#language{ datasrc: chart_languages_path(measure: @measure, language_name: @language_names) } - = submit_tag t('.update'), class: 'btn btn-small btn-primary pull-right' + .selector-section + .language-selectors + - @language_names.each_with_index do |name, index| + .language-selector-item + .lang-color-dot{ style: "background-color: ##{language_color(name)}" } + .select-wrapper + .language-dropdown-wrapper + - menu_id = "lang-dropdown-#{index}" + %button.language-dropdown-trigger{ type: 'button', data: { value: name }, aria: { haspopup: 'listbox', expanded: 'false', controls: menu_id } } + %span.selection= name + %i.icon-chevron-down + .language-dropdown-menu{ id: menu_id, role: 'listbox' } + - @languages.each do |lang_name, lang_value| + %button.dropdown-item{ type: 'button', role: 'option', data: { value: lang_value } }= lang_name + %input.language-input{ type: 'hidden', name: 'language_name[]', value: name } + + .language-selector-item + .lang-color-dot.empty + .select-wrapper + .language-dropdown-wrapper + - menu_id = 'lang-dropdown-empty' + %button.language-dropdown-trigger{ type: 'button', aria: { haspopup: 'listbox', expanded: 'false', controls: menu_id } } + %span.selection= t('.select_language') + %i.icon-chevron-down + .language-dropdown-menu{ id: menu_id, role: 'listbox' } + - @languages.each do |lang_name, lang_value| + %button.dropdown-item{ type: 'button', role: 'option', data: { value: lang_value } }= lang_name + %input.language-input{ type: 'hidden', name: 'language_name[]' } + + .update-button-wrapper + = submit_tag t('.update'), class: 'btn btn-primary update-btn' diff --git a/app/views/languages/index.html.haml b/app/views/languages/index.html.haml index 1d59953d7..51ce1794d 100644 --- a/app/views/languages/index.html.haml +++ b/app/views/languages/index.html.haml @@ -1,11 +1,9 @@ - content_for(:html_title) { t('.title') } -%h3.pull-left= t('.heading') +.languages-index-container + %h3.languages-index-title= t('.heading') -.clearfix - -.col-md-12 = render 'shared/search_dingus', collection: @languages, sort_context: 'languages', no_match_found_type: :flash = render partial: 'language', collection: @languages -= will_paginate @languages + = render 'shared/modern_pagination', collection: @languages diff --git a/app/views/languages/show.html.haml b/app/views/languages/show.html.haml index 78dac898e..dc816dc32 100644 --- a/app/views/languages/show.html.haml +++ b/app/views/languages/show.html.haml @@ -1,24 +1,25 @@ - content_for(:html_title) { t('.title', title: @language.nice_name) } - page_context[:select_side_bar] = @language.name -%h3= t('.heading') -.col-md-12 - = render 'language', language: @language, show_lang_usage: true +.languages-show-container + %h3= t('.heading') + .col-md-12 + = render 'language', language: @language, show_lang_usage: true -.col-md-6 - - exp_accounts = @language.experienced_contributors - - if exp_accounts.present? - .inset - %h3= t('.most_experience_contributors') - %p= t('.most_exp_Desc', name: @language.nice_name) - = render 'account', accounts: exp_accounts, show_commits: false + .col-md-6 + - exp_accounts = @language.experienced_contributors + - if exp_accounts.present? + .inset + %h3= t('.most_experience_contributors') + %p= t('.most_exp_Desc', name: @language.nice_name) + = render 'account', accounts: exp_accounts, show_commits: false -.col-md-6 - - active_accounts = @language.active_contributors - - if active_accounts.present? - .inset - %h3= t('.active_contributors') - %p= t('.active_contributors_desc', name: @language.nice_name, - start_date: Time.current.months_ago(2).at_beginning_of_month.strftime('%b %Y'), - end_date: Time.current.strftime('%b %Y')) - = render 'account', accounts: active_accounts, show_commits: true + .col-md-6 + - active_accounts = @language.active_contributors + - if active_accounts.present? + .inset + %h3= t('.active_contributors') + %p= t('.active_contributors_desc', name: @language.nice_name, + start_date: Time.current.months_ago(2).at_beginning_of_month.strftime('%b %Y'), + end_date: Time.current.strftime('%b %Y')) + = render 'account', accounts: active_accounts, show_commits: true diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 6b4b55486..4ddebaa2e 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -1,5 +1,5 @@ !!! -%html +%html{ lang: I18n.locale.to_s } %head %meta{ content: 'text/html; charset=UTF-8', 'http-equiv': 'Content-Type' } %meta{ charset: 'utf-8' } diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 7c5f3dbe8..5af0cca4c 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,6 +1,31 @@ !!! -%html +%html{ lang: 'en' } %head + :javascript + (function() { + var COOKIE_NAME = 'theme_preference'; + var getCookie = function(name) { + var nameEQ = name + '='; + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i].trim(); + if (cookie.indexOf(nameEQ) === 0) { + return cookie.substring(nameEQ.length); + } + } + return null; + }; + var cookieTheme = getCookie(COOKIE_NAME); + var theme = 'light'; + if (cookieTheme && (cookieTheme === 'light' || cookieTheme === 'dark')) { + theme = cookieTheme; + } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + theme = 'dark'; + } + if (theme === 'dark') { + document.documentElement.classList.add('dark'); + } + })(); - if Rails.env.production? = render partial: 'layouts/tracking_scripts/google_analytics' - page_title = content_for?(:html_title) ? yield(:html_title).to_s : t('.openhub') @@ -15,14 +40,17 @@ = yield :custom_head = stylesheet_link_tag 'application', media: 'all' = csrf_meta_tags + - unless current_user.nil? + %meta{ name: 'current-user', content: current_user.id } %body{ zoom: 1 } = yield :session_projects_banner + %header= render partial: 'layouts/partials/header' .container#page - %header= render partial: 'layouts/partials/header' = render partial: 'layouts/partials/page', locals: { responsive_layout: true } .clear %footer= render partial: 'layouts/partials/footer' = render partial: 'cookies/consent_banner' unless cookies[:cookie_consented] + = render 'shared/page_loader' = render partial: 'layouts/partials/js_scripts' diff --git a/app/views/layouts/partials/_alert.html.haml b/app/views/layouts/partials/_alert.html.haml index e48965c85..f1ae6cdcf 100644 --- a/app/views/layouts/partials/_alert.html.haml +++ b/app/views/layouts/partials/_alert.html.haml @@ -1,18 +1,13 @@ - key, klass = { error: 'alert-danger', notice: 'alert-info', success: 'alert-success', alert: 'alert-info' }.find { |k, _v| flash[k] } - if key - #flash-msg - .alert{ class: klass } - %a.close{ href: '#', 'data-dismiss' => 'alert' }= '×' - :ruby - icon = case key - when :error - 'exclamation' - when :notice, :alert - 'info' - when :success - 'ok' - else - 'warning' - end - %i{ class: "icon-small icon-#{icon}-sign" } - != flash[key] + - icon = key == :error ? 'exclamation-circle' : (key == :success ? 'check-circle' : 'info-circle') + #flash-msg{ role: 'status', 'aria-live' => 'polite', 'aria-atomic' => 'true' } + .alert{ class: klass, role: 'alert' } + .alert-accent-strip + .alert-inner + .flash-alert-icon + %i{ class: "fa fa-#{icon}" } + .flash-alert-content + != flash[key] + %button.close.flash-close{ type: 'button', 'aria-label' => 'Close' } + %i.fa.fa-times diff --git a/app/views/layouts/partials/_auth_left_panel.html.haml b/app/views/layouts/partials/_auth_left_panel.html.haml new file mode 100644 index 000000000..1e463fd4c --- /dev/null +++ b/app/views/layouts/partials/_auth_left_panel.html.haml @@ -0,0 +1,53 @@ +/ Locals: hero_lines (Array of strings), hero_accent (String), tagline (String) +.signup-left-panel + .signup-orb.signin-orb--top-left + .signup-orb.signin-orb--bottom-right + .signup-orb.signin-orb--center + + .signup-left-content + .signup-badge + .signup-badge__dot.signin-badge__dot--pulse + = t('auth.shared.trusted_by') + + %h1.signup-headline + - hero_lines.each_with_index do |line, i| + = line + - if i < hero_lines.length - 1 + %br + %br + %span.signup-headline__accent= hero_accent + + %p.signup-tagline= tagline + + .signup-benefits + .signup-benefit + .signup-benefit__icon.signin-feature--purple + %i.fa.fa-code-fork + .signup-benefit__text + %p.signup-benefit__title= t('auth.shared.benefits.projects.title') + %p.signup-benefit__desc= t('auth.shared.benefits.projects.desc') + + .signup-benefit + .signup-benefit__icon.signin-feature--amber + %i.fa.fa-users + .signup-benefit__text + %p.signup-benefit__title= t('auth.shared.benefits.contributors.title') + %p.signup-benefit__desc= t('auth.shared.benefits.contributors.desc') + + .signup-benefit + .signup-benefit__icon.signin-feature--green + %i.fa.fa-shield + .signup-benefit__text + %p.signup-benefit__title= t('auth.shared.benefits.security.title') + %p.signup-benefit__desc= t('auth.shared.benefits.security.desc') + + .signup-benefit + .signup-benefit__icon.signin-feature--pink + %i.fa.fa-bolt + .signup-benefit__text + %p.signup-benefit__title= t('auth.shared.benefits.activity.title') + %p.signup-benefit__desc= t('auth.shared.benefits.activity.desc') + + .signup-left-footer + %p.signin-testimonial= t('auth.shared.testimonial') + %p.signin-testimonial-attr= t('auth.shared.testimonial_attr') diff --git a/app/views/layouts/partials/_fluid_footer.html.haml b/app/views/layouts/partials/_fluid_footer.html.haml index 64b7d1502..1f32f7228 100644 --- a/app/views/layouts/partials/_fluid_footer.html.haml +++ b/app/views/layouts/partials/_fluid_footer.html.haml @@ -10,25 +10,25 @@ = "ABOUT BLACK DUCK" %p %p - %a{ href: 'https://www.blackduck.com/solutions/application-security-testing.html' }= 'Application Security Testing' + %a{ href: 'https://www.blackduck.com/solutions/application-security-testing.html', target: '_blank', rel: 'noopener noreferrer' }= 'Application Security Testing' %p - %a{ href: 'https://www.blackduck.com/services.html' }= 'Application Security Services' + %a{ href: 'https://www.blackduck.com/services.html', target: '_blank', rel: 'noopener noreferrer' }= 'Application Security Services' %p - %a{ href: 'https://www.blackduck.com/services/security-program/strategy-planning.html' }= 'AppSec Program Development' + %a{ href: 'https://www.blackduck.com/services/security-program/strategy-planning.html', target: '_blank', rel: 'noopener noreferrer' }= 'AppSec Program Development' %p - %a{ href: 'https://www.blackduck.com/training.html', target: '_blank' }= 'Training' + %a{ href: 'https://www.blackduck.com/training.html', target: '_blank', rel: 'noopener noreferrer' }= 'Training' .footer-right = "ABOUT OPEN HUB" %p %p - %a{ href: 'https://community.blackduck.com/s/black-duck-open-hub' }= t :forum + %a{ href: 'https://community.blackduck.com/s/black-duck-open-hub', target: '_blank', rel: 'noopener noreferrer' }= t :forum %p - %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Terms-of-Use' }= t :terms + %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Terms-of-Use', target: '_blank', rel: 'noopener noreferrer' }= t :terms %p - %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Open-Hub-Privacy-Policy' }= t :privacy_blog + %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Open-Hub-Privacy-Policy', target: '_blank', rel: 'noopener noreferrer' }= t :privacy_blog %p - %a{ href: 'https://github.com/blackducksoftware/ohloh-ui', target: '_blank' }= t('copyright.source_code') + %a{ href: 'https://github.com/blackducksoftware/ohloh-ui', target: '_blank', rel: 'noopener noreferrer' }= t('copyright.source_code') %p %a{ href: "mailto:#{raw_email}".html_safe }= t :contact_us @@ -38,5 +38,5 @@ = Time.current.year %span{ itemscope: '', itemtype: 'http://schema.org/CreativeWork' } %span{ itemprop: 'publisher' } - %a{ href: 'http://www.blackduck.com', target: '_blank' }= "Black Duck Software, Inc." + %a{ href: 'http://www.blackduck.com', target: '_blank', rel: 'noopener noreferrer' }= "Black Duck Software, Inc." = t('copyright.rights_text') diff --git a/app/views/layouts/partials/_footer.html.haml b/app/views/layouts/partials/_footer.html.haml index 53b3e40d7..bd1da6c05 100644 --- a/app/views/layouts/partials/_footer.html.haml +++ b/app/views/layouts/partials/_footer.html.haml @@ -1,40 +1,51 @@ - raw_email = '%69%6Efo%40O%70en%48u%62.%6E%65t' -.footer-left - %a.logo_link{ href: 'https://www.blackduck.com' } - %img.logo_img{ itemprop: 'image', src: image_path('logo/BlackDuckLogo.svg'), alt: 'Open Hub' } +.footer-container + .footer-grid + .footer-left + %a.logo_link{ href: 'https://www.blackduck.com', target: '_blank', rel: 'noopener noreferrer' } + %img.logo_img{ itemprop: 'image', src: image_path('logo/BlackDuckLogo-OnDark.svg'), alt: 'Black Duck' } -.footer-mid - = "ABOUT BLACK DUCK" - %p - %p - %a{ href: 'https://www.blackduck.com/solutions/application-security-testing.html' }= 'Application Security Testing' - %p - %a{ href: 'https://www.blackduck.com/services.html' }= 'Application Security Services' - %p - %a{ href: 'https://www.blackduck.com/services/security-program/strategy-planning.html' }= 'AppSec Program Development' - %p - %a{ href: 'https://www.blackduck.com/training.html', target: '_blank' }= 'Training' + .footer-mid + %h3 ABOUT BLACK DUCK + %ul + %li + %a{ href: 'https://www.blackduck.com/solutions/application-security-testing.html', target: '_blank', rel: 'noopener noreferrer' }= 'Application Security Testing' + %li + %a{ href: 'https://www.blackduck.com/services.html', target: '_blank', rel: 'noopener noreferrer' }= 'Application Security Services' + %li + %a{ href: 'https://www.blackduck.com/services/security-program/strategy-planning.html', target: '_blank', rel: 'noopener noreferrer' }= 'AppSec Program Development' + %li + %a{ href: 'https://www.blackduck.com/training.html', target: '_blank', rel: 'noopener noreferrer' }= 'Training' -.footer-right - = "ABOUT OPEN HUB" - %p - %p - %a{ href: 'https://community.blackduck.com/s/black-duck-open-hub' }= t :forum - %p - %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Terms-of-Use' }= t :terms - %p - %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Open-Hub-Privacy-Policy' }= t :privacy_blog - %p - %a{ href: 'https://github.com/blackducksoftware/ohloh-ui', target: '_blank' }= t('copyright.source_code') - %p - %a{ href: "mailto:#{raw_email}".html_safe }= t :contact_us + .footer-right + %h3 ABOUT OPEN HUB + %ul + %li + %a{ href: 'https://community.blackduck.com/s/black-duck-open-hub', target: '_blank', rel: 'noopener noreferrer' }= t :forum + %li + %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Terms-of-Use', target: '_blank', rel: 'noopener noreferrer' }= t :terms + %li + %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Open-Hub-Privacy-Policy', target: '_blank', rel: 'noopener noreferrer' }= t :privacy_blog + %li + %a{ href: 'https://github.com/blackducksoftware/ohloh-ui', target: '_blank', rel: 'noopener noreferrer' }= t('copyright.source_code') + %li + %a{ href: "mailto:#{raw_email}".html_safe }= t :contact_us - -.footer-bottom - %sup © - = Time.current.year - %span{ itemscope: '', itemtype: 'http://schema.org/CreativeWork' } - %span{ itemprop: 'publisher' } - %a{ href: 'http://www.blackduck.com', target: '_blank' }= "Black Duck Software, Inc." - = t('copyright.rights_text') + .footer-bottom + .footer-bottom-content + .copyright + %sup © + = Time.current.year + %span{ itemscope: '', itemtype: 'http://schema.org/CreativeWork' } + %span{ itemprop: 'publisher' } + %a{ href: 'http://www.blackduck.com', target: '_blank', rel: 'noopener noreferrer' }= "Black Duck Software, Inc." + = t('copyright.rights_text') + .follow-us + %span Follow us: + :ruby + twitter_url = 'https://x.com/intent/follow?original_referer=' + twitter_url += CGI.escape request.url + twitter_url += '®ion=follow_link&screen_name=bdopenhub&source=followbutton&variant=2.0' + %a{ href: twitter_url, target: '_blank', 'aria-label' => 'Follow us on Twitter' } + %i.icon-twitter diff --git a/app/views/layouts/partials/_footer_nav.html.haml b/app/views/layouts/partials/_footer_nav.html.haml index b5b555825..c011f5576 100644 --- a/app/views/layouts/partials/_footer_nav.html.haml +++ b/app/views/layouts/partials/_footer_nav.html.haml @@ -5,19 +5,9 @@ .clearfix - if responsive_layout && page_context[:footer_menu_list] .clearfix - .row.fluid.mezzo - .footer-navigation.fluid.col-xs-12 - .row - .col-md-12 - .mezzo.margin_bottom_5 - .clearfix - .col-xs-10.col-xs-offset-1 - = render 'layouts/partials/navigator' + .footer-navigation + = render 'layouts/partials/navigator' - elsif page_context[:footer_menu_list] .clearfix.margin_top_15 - .margin_left_20.margin_right_20 - .footer-navigation - .row - .col-md-12 - .mezzo.margin_bottom_5 - = render 'layouts/partials/navigator' + .footer-navigation + = render 'layouts/partials/navigator' diff --git a/app/views/layouts/partials/_header.html.haml b/app/views/layouts/partials/_header.html.haml index b94cc59f4..8e37eba2e 100644 --- a/app/views/layouts/partials/_header.html.haml +++ b/app/views/layouts/partials/_header.html.haml @@ -1,4 +1,4 @@ = render partial: 'layouts/partials/mast' = render partial: 'layouts/partials/menubar' = render partial: 'layouts/partials/stacked_nag', locals: { reminder: find_nag_reminder } -= render partial: 'layouts/partials/alert' unless page_context[:page_header] += render partial: 'layouts/partials/alert' unless page_context[:page_header] || page_context[:suppress_flash] diff --git a/app/views/layouts/partials/_mast.html.haml b/app/views/layouts/partials/_mast.html.haml index f28f75c58..c48b0cb50 100644 --- a/app/views/layouts/partials/_mast.html.haml +++ b/app/views/layouts/partials/_mast.html.haml @@ -1,58 +1,123 @@ +:ruby + explore_projects_class = params[:action] == 'explore/projects' ? 'select' : '' + explore_orgs_class = params[:action] == 'explore/orgs' ? 'select' : '' + tools_select = params[:action] == 'tools' ? 'select' : '' + .navbar - %span{ itemscope: '', itemtype: 'http://schema.org/CreativeWork' } - %span.hidden{ itemprop: 'author' } openhub.net - %span.hidden{ itemprop: 'publisher' } Black Duck Software, Inc. - %span + .navbar-container + %span.hidden{ itemscope: '', itemtype: 'http://schema.org/CreativeWork' } + %span.hidden{ itemprop: 'author' } openhub.net + %span.hidden{ itemprop: 'publisher' } Black Duck Software, Inc. + .logo-and-brand %div.logo-div - %a.logo_link{ href: 'https://www.blackduck.com' } - %img.logo_img{ itemprop: 'image', src: image_path('logo/BlackDuckLogo.svg'), alt: 'Open Hub' } - %div.spacing-div + %a.logo_link{ href: root_path } + %img.logo_img{ itemprop: 'image', src: image_path('logo/BlackDuckLogo-OnDark.svg'), alt: 'Black Duck Open Hub' } %div.separator-div - %div.spacing-div %div.company-div %a{ href: root_path } %span.navbar_large_text Open Hub - %div.actions-div - %ul#top_nav_actions - %li.twitter_follow - :ruby - twitter_url = 'https://twitter.com/intent/follow?original_referer=' - twitter_url += CGI.escape request.url - twitter_url += '®ion=follow_link&screen_name=bdopenhub&source=followbutton&variant=2.0' - %a.btn.follow_btn{ href: twitter_url, target: '_blank' } - %i.icon-twitter - %p.follow! #{t(:follow)} @ - %p.twitter-text  OH  - - if logged_in? - %li - .dropdown#logged_user_menu - %a{ 'data-toggle' => 'dropdown', :href => '#', :class => 'dropdown-toggle navbar-text link_no_underline' } - %i.icon-user - = h(current_user.name) - %i.icon-caret-down - %ul.dropdown-menu.user-menu.pull-right.dropdown-menu.dropdown-yellow.dropdown-caret.dropdown-closer - %li - = link_to(" #{t(:profile)}".html_safe, account_path(current_user).to_s) + %nav.nav-menu + %ul.new_main_menu{ class: page_context[:select_top_menu_nav] } + %li.menu_item.projects + = link_to t(:projects_menu), projects_explores_path, class: explore_projects_class + %li.menu_item.people + = link_to t(:people_menu), people_path, class: params[:action] == 'people' ? 'select' : '' + %li.menu_item.organizations + = link_to t(:organizations_menu), orgs_explores_path, class: explore_orgs_class + %li.menu_item.tools + = link_to t(:tools_menu), tools_path, class: tools_select + %li.menu_item.blog + %a{ href: 'https://community.blackduck.com/s/black-duck-open-hub?tabset-c30ff=d9d77', target: '_blank', rel: 'noopener noreferrer' }= t :blog + %li.menu_item.bdsa + %a{ href: bdsa_vulnerabilities_path, target: '_blank' }= t :bdsa + %div.actions-div + %button#theme-toggle.theme-toggle-btn{ 'aria-label' => 'Toggle theme' } + %i#theme-icon-moon.icon-moon.theme-icon + %svg#theme-icon-sun.theme-icon-light.theme-icon.hidden{ xmlns: 'https://www.w3.org/2000/svg', width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'aria-hidden': 'true' } + %circle{ cx: '12', cy: '12', r: '5' } + %line{ x1: '12', y1: '1', x2: '12', y2: '3' } + %line{ x1: '12', y1: '21', x2: '12', y2: '23' } + %line{ x1: '4.22', y1: '4.22', x2: '5.64', y2: '5.64' } + %line{ x1: '18.36', y1: '18.36', x2: '19.78', y2: '19.78' } + %line{ x1: '1', y1: '12', x2: '3', y2: '12' } + %line{ x1: '21', y1: '12', x2: '23', y2: '12' } + %line{ x1: '4.22', y1: '19.78', x2: '5.64', y2: '18.36' } + %line{ x1: '18.36', y1: '5.64', x2: '19.78', y2: '4.22' } + %ul#top_nav_actions.desktop-actions + - if logged_in? + %li + .dropdown#logged_user_menu + %a{ 'data-toggle' => 'dropdown', :href => '#', :class => 'dropdown-toggle navbar-text link_no_underline' } + %i.icon-user + = h(current_user.name) + %i.icon-caret-down + %ul.dropdown-menu.user-menu.pull-right.dropdown-menu.dropdown-yellow.dropdown-caret.dropdown-closer + %li + = link_to(" #{t(:profile)}".html_safe, account_path(current_user).to_s) + %li + = link_to(" #{t(:settings)}".html_safe, settings_account_path(current_user).to_s) + - if current_user_is_admin? %li - = link_to(" #{t(:settings)}".html_safe, settings_account_path(current_user).to_s) - - if current_user_is_admin? - %li - = link_to(" #{t(:admin_dashboard)}".html_safe, admin_accounts_path) - %li.divider - %li - = link_to(" #{t(:logout)}".html_safe, sessions_path, method: :delete) - - else - %li - %a.btn.btn-mini.btn-primary.btn-header{ href: new_session_path }= t :sign_in + = link_to(" #{t(:admin_dashboard)}".html_safe, admin_accounts_path) + %li.divider + %li + = link_to(" #{t(:logout)}".html_safe, sessions_path, method: :delete) + - else + %li + %a.btn.btn-primary.btn-header{ href: new_session_path } + %i.icon-user + = t :sign_in + %li + %a.btn.btn-outline.btn-join-now{ href: new_account_path } + Join Now + %button#mobile-menu-toggle.mobile-menu-toggle{ 'aria-label' => 'Menu' } + %i#mobile-menu-icon.icon-reorder + #mobile-menu.mobile-menu + .mobile-menu-content + %ul.mobile-menu-items + %li + = link_to t(:projects_menu), projects_explores_path, class: explore_projects_class + %li + = link_to t(:people_menu), people_path, class: params[:action] == 'people' ? 'select' : '' + %li + = link_to t(:organizations_menu), orgs_explores_path, class: explore_orgs_class + %li + = link_to t(:tools_menu), tools_path, class: tools_select + %li + %a{ href: 'https://community.blackduck.com/s/black-duck-open-hub?tabset-c30ff=d9d77', target: '_blank', rel: 'noopener noreferrer' }= t :blog + %li + %a{ href: bdsa_vulnerabilities_path, target: '_blank' }= t :bdsa + - if logged_in? + %ul.mobile-menu-user + %li + = link_to(" #{t(:profile)}".html_safe, account_path(current_user).to_s) + %li + = link_to(" #{t(:settings)}".html_safe, settings_account_path(current_user).to_s) + - if current_user_is_admin? %li - %a.btn.btn-mini.btn-success.btn-header{ href: new_account_path }= t :join_now + = link_to(" #{t(:admin_dashboard)}".html_safe, admin_accounts_path) + %li + = link_to(" #{t(:logout)}".html_safe, sessions_path, method: :delete) + - else + .mobile-menu-signin + %a.btn.btn-primary.btn-mobile-signin{ href: new_session_path } + %i.icon-user + = t :sign_in + %a.btn.btn-outline.btn-mobile-join-now{ href: new_account_path } + Join Now - if read_only_mode? - %div - .clear -   - .alert.alert-info - %h3= t 'header.maintenance_in_progress_title' - %p= t 'header.maintenance_in_progress_body_1' - %p= t 'header.maintenance_in_progress_body_2' + .maintenance-banner + .maintenance-banner-strip + .maintenance-banner-inner + .maintenance-banner-icon + %i.icon-warning-sign + .maintenance-banner-content + %h3= t 'header.maintenance_in_progress_title' + %p= t 'header.maintenance_in_progress_body_1' + %p= t 'header.maintenance_in_progress_body_2' + .maintenance-banner-footer + = link_to forums_path, class: 'maintenance-banner-link' do + Help Forum + %i.icon-chevron-right diff --git a/app/views/layouts/partials/_menubar.html.haml b/app/views/layouts/partials/_menubar.html.haml index da4d58e94..38957cace 100644 --- a/app/views/layouts/partials/_menubar.html.haml +++ b/app/views/layouts/partials/_menubar.html.haml @@ -15,7 +15,7 @@ %li.menu_item.tools = link_to t(:tools_menu), tools_path, class: tools_select %li.menu_item.blog - %a{ href: 'https://community.blackduck.com/s/black-duck-open-hub?tabset-c30ff=d9d77', target: '_blank' }= t :blog + %a{ href: 'https://community.blackduck.com/s/black-duck-open-hub?tabset-c30ff=d9d77', target: '_blank', rel: 'noopener noreferrer' }= t :blog %li.menu_item.bdsa %a{ href: bdsa_vulnerabilities_path, target: '_blank' }= t :bdsa = render 'shared/search.html.haml' diff --git a/app/views/layouts/partials/_navigator.html.haml b/app/views/layouts/partials/_navigator.html.haml index ecb3ef3be..616c4e88e 100644 --- a/app/views/layouts/partials/_navigator.html.haml +++ b/app/views/layouts/partials/_navigator.html.haml @@ -1,27 +1,69 @@ -- page_context[:footer_menu_list].each do |section| - .actions.margin_top_20.margin_bottom_40{ class: ('col-md-3 col-xs-3 no_padding' if page_context[:nav_type].eql?('footer_nav')) } - %ul.nav.nav-stacked.nav-pills +.footer-nav-grid + - page_context[:footer_menu_list].each do |section| + .nav-column - header, *links = section - header_class, header_name, header_url, header_type = header - %h4{ class: [('selected' if header_class == page_context[:select_footer_nav]), ('linked' if header_url)] } + + .column-header - if header_url - %a{ href: header_url } + = link_to header_url do %i{ class: "icon #{header_class}" } - = header_name + %h4{ class: [('selected' if header_class == page_context[:select_footer_nav]), 'linked'] }= header_name - else %i{ class: "icon #{header_class}" } - = header_name + %h4{ class: ('selected' if header_class == page_context[:select_footer_nav]) }= header_name + - if header_type.eql?('select') = select(:language, :url, options_for_select(links), {}, id: 'nav-select', class: 'chzn-select') - else - - links.each do |link| - - link_class, link_name, link_url = link - %li.footer-nav{ class: ('nav-active' if link_class == page_context[:select_footer_nav]) } - - if link_name == 'SBOM' - = link_to h(link_name), link_url, remote: true, - data: { toggle: 'modal', target: '#sbom-details.modal', keyboard: true } - - else - %a{ href: link_url }= link_name + %ul + - links.each do |link| + - link_class, link_name, link_url = link + %li{ class: ('nav-active' if link_class == page_context[:select_footer_nav]) } + - if link_name == 'SBOM' + = link_to h(link_name), link_url, remote: true, + data: { toggle: 'modal', target: '#sbom-details.modal', keyboard: true } + - else + %a{ href: link_url }= link_name + +.footer-nav-accordion + - page_context[:footer_menu_list].each do |section| + .accordion-item + - header, *links = section + - header_class, header_name, header_url, header_type = header + + %button.accordion-header-btn{ onclick: "toggleFooterAccordion(this)" } + .header-left + %i{ class: "icon #{header_class}" } + %span{ class: ('selected' if header_class == page_context[:select_footer_nav]) }= header_name + %svg.chevron{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %polyline{ points: "6 9 12 15 18 9" } + + - if header_type.eql?('select') + .accordion-content-footer{ style: "display: none;" } + = select(:language, :url, options_for_select(links), {}, id: 'nav-select-mobile', class: 'chzn-select') + - else + .accordion-content-footer{ style: "display: none;" } + %ul + - links.each do |link| + - link_class, link_name, link_url = link + %li{ class: ('nav-active' if link_class == page_context[:select_footer_nav]) } + - if link_name == 'SBOM' + = link_to h(link_name), link_url, remote: true, + data: { toggle: 'modal', target: '#sbom-details.modal', keyboard: true } + - else + %a{ href: link_url }= link_name + +:javascript + function toggleFooterAccordion(button) { + const content = button.nextElementSibling; + const chevron = button.querySelector('.chevron'); + const isExpanded = content.style.display === 'block'; + + content.style.display = isExpanded ? 'none' : 'block'; + chevron.classList.toggle('expanded', !isExpanded); + } + #avatar-dialog.modal.pmd-modal.fade#sbom-details{"aria-hidden" => "true", :style => "display: none;", :tabindex => "-1"} .modal-dialog.modal-md .modal-content diff --git a/app/views/layouts/responsive_project_layout.html.haml b/app/views/layouts/responsive_project_layout.html.haml index 63f832a98..5a1be0dab 100644 --- a/app/views/layouts/responsive_project_layout.html.haml +++ b/app/views/layouts/responsive_project_layout.html.haml @@ -1,6 +1,8 @@ !!!5 -%html +%html{ lang: 'en' } %head + :javascript + try { if (localStorage.getItem('theme') === 'dark') document.documentElement.classList.add('dark'); } catch(e) {} - page_title = content_for?(:html_title) ? yield(:html_title).to_s : t('layouts.application.openhub') %title= sanitize(page_title) %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' } @@ -20,6 +22,6 @@ %header= render partial: 'layouts/partials/header' = render partial: 'layouts/partials/page', locals: { responsive_layout: true } .clear - %footer.fluid_footer= render partial: 'layouts/partials/fluid_footer' - = render 'cookies/consent_banner' unless cookies[:cookie_consented] + %footer= render partial: 'layouts/partials/footer' + = render 'cookies/consent_banner' unless cookies[:cookie_consented] = render partial: 'layouts/partials/js_scripts' diff --git a/app/views/layouts/vulnerability.html.haml b/app/views/layouts/vulnerability.html.haml index a3746d054..21bdaf504 100644 --- a/app/views/layouts/vulnerability.html.haml +++ b/app/views/layouts/vulnerability.html.haml @@ -141,6 +141,6 @@ = Time.current.year %span{ itemscope: '', itemtype: 'https://schema.org/CreativeWork' } %span{ itemprop: 'publisher' } - %a{ href: 'https://www.blackduck.com', target: '_blank', rel: 'noopener' }= "Black Duck Software, Inc." + %a{ href: 'https://www.blackduck.com', target: '_blank', rel: 'noopener noreferrer' }= "Black Duck Software, Inc." = t('copyright.rights_text') diff --git a/app/views/licenses/_fields.haml b/app/views/licenses/_fields.haml index 554a8d181..14b1014e4 100644 --- a/app/views/licenses/_fields.haml +++ b/app/views/licenses/_fields.haml @@ -1,37 +1,37 @@ .control-group - %label.control-label.required= t('.name') + = f.label :name, t('.name'), class: 'control-label required' .controls - = f.text_field :name, class: 'col-md-11' + = f.text_field :name, class: 'form-control' - error_tag @license, :name %p.help-block= t('.name_help') .control-group - %label.control-label.required= t('.url_name') + = f.label :vanity_url, t('.url_name'), class: 'control-label required' .controls - .input-prepend.pull-left + .input-prepend.license-url-prepend %span.add-on= 'http://www.openhub.net/licenses/' - - read_only = @license.persisted? ? true : false - = f.text_field :vanity_url, readonly: read_only, :class => 'check-availability col-md-6', - autocomplete: 'off', - 'data-original-value' => License.find_by(id: @license).try(:vanity_url), - 'data-ajax-path' => license_check_availabilities_path, - 'data-preview-base-url' => licenses_url + - read_only = @license.persisted? ? true : false + = f.text_field :vanity_url, readonly: read_only, class: 'form-control check-availability', + autocomplete: 'off', + 'data-original-value' => License.find_by(id: @license).try(:vanity_url), + 'data-ajax-path' => license_check_availabilities_path, + 'data-preview-base-url' => licenses_url = render 'shared/availability_preview' - error_tag @license, :vanity_url, class: 'error url_name' %p.help-block= t('.vanity_url_help') .control-group - %label.control-label= t('.homepage') + = f.label :url, t('.homepage'), class: 'control-label' .controls - = f.text_field :url, class: 'col-md-11' + = f.text_field :url, class: 'form-control' - error_tag @license, :url %p.help-block= t('.homepage_help') .control-group - %label.control-label= t('.license_text') + = f.label :description, t('.license_text'), class: 'control-label' .controls = find_and_preserve(f.text_area(:description, max_length: 50_000, rows: 10, - class: 'col-md-11 no_padding_right')) + class: 'form-control edit-description')) - error_tag @license, :description .clearfix %p.help-block= t('.license_help') @@ -41,6 +41,6 @@ %input.btn.btn-primary{ type: 'submit', value: btn_value } .pull-right.margin_right_35 - if current_user_is_admin? - %label.checkbox - = f.check_box :locked, choice: 'Locked' + = f.label :locked, class: 'checkbox' do + = f.check_box :locked = t('.locked') diff --git a/app/views/licenses/edit.html.haml b/app/views/licenses/edit.html.haml index 96588bce2..16b07ac59 100644 --- a/app/views/licenses/edit.html.haml +++ b/app/views/licenses/edit.html.haml @@ -1,14 +1,17 @@ - content_for(:html_title) { t('.title', name: @license.name) } -#license - %h1 +#license-edit-page + .license-edit-breadcrumb = link_to t('.license'), licenses_path -  : + %span.license-edit-sep / = link_to h(truncate(@license.name, length: 42)), license_url(@license), title: @license.name - %h2= t('.edit_license') - -.col-md-10 - .well + %span.license-edit-sep / + %span= t('.edit_license') + .license-edit-header + %h1.license-edit-title + %i.fa.fa-pencil + = t('.edit_license') + .oh-card.col-md-8 = form_for @license do |f| - %fieldset + %fieldset.margin_left_15 = render 'fields', f: f diff --git a/app/views/licenses/index.html.haml b/app/views/licenses/index.html.haml index b2d0288f1..1a1ba1aba 100644 --- a/app/views/licenses/index.html.haml +++ b/app/views/licenses/index.html.haml @@ -1,35 +1,38 @@ - content_for(:html_title) { t('.title') } -#license - %h1.col-md-9.no_padding_left= t('.licenses') - .pull-right.margin_top_15 - - if @project - = link_to t('.edit_project_licenses'), edit_project_url(@project), class: 'btn btn-primary' - - else - = link_to t('.add_new_license'), new_license_path, class: 'btn btn-mini btn-primary' - .clearfix +#licenses-index-page + .licenses-page-header + %h1.licenses-page-title + %i.fa.fa-balance-scale + = t('.license') + .licenses-page-actions + - if @project + = link_to t('.edit_project_licenses'), edit_project_url(@project), class: 'btn btn-primary' + - else + = link_to t('.add_new_license'), new_license_path, class: 'btn btn-primary' - if @project && @licenses.empty? - %p= t('.no_license_for_project', name: h(@project.name)) - %p - = t('.click') - = link_to t('.here'), edit_project_url(@project) - = t('.click_more') + .licenses-empty-state + %i.fa.fa-folder-open-o.licenses-empty-icon + %p.licenses-empty-text= t('.no_license_for_project', name: h(@project.name)) + %p.licenses-empty-link + = t('.click') + = link_to t('.here'), edit_project_url(@project) + = t('.click_more') - else = render 'shared/search_dingus', collection: @licenses, sort_context: nil, no_match_found_type: :none %span{ itemscope: '', itemtype: 'http://schema.org/CreativeWork' } %span{ itemprop: 'publishingPrinciples' } - %table.table.table-striped.table-condensed - %tbody - %tr{ style: 'display: none' } - %td - - @licenses.each do |license| - %tr - %td= link_to h(license.name), license_url(license) + .licenses-grid + - @licenses.each do |license| + .oh-card.oh-card--hoverable.license-card + .license-card-body + %i.fa.fa-file-text-o.license-card-icon + = link_to h(license.name), license_url(license), class: 'license-card-link' -= render partial: 'shared/alert', - locals: { message: t('shared.search_dingus.no_match_found.no_match') } if @licenses.blank? && params[:query].present? + = render partial: 'shared/alert', + locals: { message: t('shared.search_dingus.no_match_found.no_match') } if @licenses.blank? && params[:query].present? -= will_paginate @licenses + = render 'shared/modern_pagination', collection: @licenses diff --git a/app/views/licenses/new.html.haml b/app/views/licenses/new.html.haml index e32568437..61cbce0d7 100755 --- a/app/views/licenses/new.html.haml +++ b/app/views/licenses/new.html.haml @@ -1,13 +1,15 @@ - content_for(:html_title) { t('.title') } -#license - %h1.margin_bottom_20 +#license-edit-page + .license-edit-breadcrumb = link_to t('.licenses'), licenses_path -  : - = t('.new_license') - - .col-md-10 - .well - = form_for @license do |f| - %fieldset - = render 'fields', f: f + %span.license-edit-sep / + %span= t('.new_license') + .license-edit-header + %h1.license-edit-title + %i.fa.fa-plus + = t('.new_license') + .oh-card.col-md-8 + = form_for @license do |f| + %fieldset + = render 'fields', f: f diff --git a/app/views/licenses/show.html.haml b/app/views/licenses/show.html.haml index b3dab738d..176669fc0 100644 --- a/app/views/licenses/show.html.haml +++ b/app/views/licenses/show.html.haml @@ -1,32 +1,39 @@ - content_for(:html_title) { t('.title', name: @license.name) } -#license - %h1.col-md-9.no_padding_left +#license-show-page + .license-show-breadcrumb = link_to t('.license'), licenses_path -  : - = link_to h(truncate(@license.name, length: 42)), license_url(@license), title: @license.name + %span.license-show-sep / + %span.license-show-name{ title: @license.name }= h(truncate(@license.name, length: 60)) - .pull-right.licenses_button_groups.margin_top_10 - - if current_user_is_admin? || !@license.locked - = link_to t('.edit_license'), edit_license_path(@license), - class: "btn btn-info btn-mini #{'needs_login' unless logged_in?}" + .oh-card.margin_top_20 + .license-show-header + %h1.license-show-title + %i.fa.fa-balance-scale + = h(@license.name) + .license-show-actions + - if current_user_is_admin? || !@license.locked + = link_to t('.edit_license'), edit_license_path(@license), + class: "btn btn-default btn-sm license-action-btn #{'needs_login' unless logged_in?}" + = link_to t('.view_history'), license_edits_path(@license), class: 'btn btn-default btn-sm license-action-btn' + - if @license.url.present? + = link_to h(@license.url), class: 'btn btn-default btn-sm license-action-btn', target: '_blank', rel: 'noopener noreferrer' do + %i.fa.fa-external-link + = t('.home_page') - = link_to t('.view_history'), license_edits_path(@license), class: 'btn btn-info btn-mini' - .margin_top_5 - = icon_button(@license.url, icon: 'external-link', text: t('.home_page'), size: 'mini', - type: 'info', target: '_blank', style: 'width: 166px') + .license-show-body + - if @license.description.blank? + .license-no-description + %i.fa.fa-info-circle + = link_to t('.provide_license'), edit_license_path(@license) + - else + .license-description= simple_format(html_escape(@license.description)) -.clearfix + - if @license.url.present? + .license-url-block + %i.fa.fa-link + %span= t('.read_more') + = link_to h(@license.url), h(@license.url), target: '_blank', rel: 'noopener noreferrer' -- if @license.description.blank? - %p= link_to t('.provide_license'), edit_license_path(@license) - -- else - .margin_top_20 - %p!= simple_format(html_escape(@license.description)) - -- if @license.url.present? - %p - = t('.read_more') - = link_to h(@license.url), h(@license.url) -= link_to t('.add_new_license'), new_license_path, class: 'btn btn-small btn-primary margin_bottom_20' + .license-show-footer + = link_to t('.add_new_license'), new_license_path, class: 'btn btn-primary' diff --git a/app/views/links/_about_links.html.haml b/app/views/links/_about_links.html.haml index f8bd5061d..695afb172 100644 --- a/app/views/links/_about_links.html.haml +++ b/app/views/links/_about_links.html.haml @@ -1,8 +1,16 @@ -%h4.margin_bottom_15 - %span.soft= t('about') - = t('links.title').pluralize -.col-md-8 - %ul.margin_left_20 - %li= t('.first') - %li= t('.second') - %li= t('.third') +.about-project-basics-card + .card-header + %h4.card-title + %span.soft= t('.about') + = t('links.title').pluralize + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + + .card-content + .about-content-wrapper + .col-md-6.margin_left_30 + %ul + %li= t('.first') + %li= t('.second') + %li= t('.third') + diff --git a/app/views/links/_fields.haml b/app/views/links/_fields.haml index d86ec78b6..7b25d8c91 100644 --- a/app/views/links/_fields.haml +++ b/app/views/links/_fields.haml @@ -1,22 +1,21 @@ -%fieldset +%fieldset.margin_left_10.link-form - if @category_name - %h2 + %h3 = @category_name = t('links.title') - .control-group - %label.control-label.required - = t('title') + .control-group.margin_top_10 + %label.control-label.required{ for: 'link_title' }= t('title') .controls - = f.text_field :title, class: 'col-md-6' + = f.text_field :title, class: 'form-control' - error_tag @link, :title %p.help-block= t('help.max_characters', number: 60) .control-group - %label.control-label.required= t('links.title') + %label.control-label.required{ for: 'link_url' }= t('links.title') - read_only = (@link.new_record? ? false : true) - title_text = (@link.new_record? ? '' : t('.url_edit')) .controls - = f.text_field :url, class: 'col-md-6', readonly: read_only, rel: 'tooltip', title: title_text + = f.text_field :url, class: 'form-control', readonly: read_only, rel: 'tooltip', title: title_text - error_tag @link, :url %p.help-block= t('.full_url') @@ -24,13 +23,15 @@ = f.hidden_field :link_category_id, value: params[:category_id] || @link.link_category_id - else .control-group - %label.control-label.required= t('category') + %label.control-label.required{ for: 'link_link_category_id' }= t('category') .controls - = f.select :link_category_id, options_for_select(@categories) + = f.select :link_category_id, options_for_select(@categories), {}, class: 'form-control' .actions - %input.btn.btn-primary.btn-xs{ type: 'submit', + %input.btn.btn-primary.btn-md{ type: 'submit', value: @link.new_record? ? t('save_model', model: t('links.title')) : t('save_changes') } - unless @link.new_record? - = link_to bootstrap_icon('icon-trash', t('.remove')), project_link_path(@project, @link), method: :delete, - data: { confirm: t(:are_you_sure) }, class: 'btn btn-xs btn-danger' + = link_to project_link_path(@project, @link), method: :delete, + data: { confirm: t(:are_you_sure) }, class: 'btn btn-md btn-danger' do + %i.icon-trash + = t('.remove') diff --git a/app/views/links/edit.html.haml b/app/views/links/edit.html.haml index 409554571..ed0b495da 100644 --- a/app/views/links/edit.html.haml +++ b/app/views/links/edit.html.haml @@ -8,9 +8,8 @@ = link_to t('links.title').pluralize, project_links_path(@project) \: = t(:edit) - = project_analysis_timestamp(@project) -.well.col-md-offset-1.col-md-8 +.oh-card.col-md-8 = form_for([@project, @link]) do |f| = render partial: 'fields', locals: { f: f } diff --git a/app/views/links/index.html.haml b/app/views/links/index.html.haml index 71092d2f0..8b95fd94a 100644 --- a/app/views/links/index.html.haml +++ b/app/views/links/index.html.haml @@ -1,55 +1,50 @@ + :ruby has_permission = logged_in? && @project.edit_authorized? content_for(:html_title) { t('.page_title', name: @project.name) } page_context[:select_top_menu_nav] = 'select_projects' page_context[:select_footer_nav] = nil -.project_content_title - %h2.pull-left - = link_to t(:settings), settings_project_path(@project) - \: - = t('links.title').pluralize - = project_analysis_timestamp(@project) += render partial: 'layouts/partials/alert' + +.links-index-page + .links-page-header + %h2.links-page-title + = link_to t(:settings), settings_project_path(@project), class: 'links-settings-link' + %span.links-title-sep : + = t('links.title').pluralize -- if @links.empty? - - flash.now[:notice] = t('.empty_message') + - if @links.empty? + - flash.now[:notice] = t('.empty_message') -.row - .links.center-block + .links-categories-grid - Link::CATEGORIES.each do |category_name, category_id| - links = @links.collect { |l| l if l.link_category_id == category_id }.compact - .well - %h3.strong.pull-left.margin_bottom_15.margin_top_0 - = category_name - .clear_both - .row - - if links.empty? - .col-md-4.margin_bottom_15 - %i= t('.no_links') - .clearfix + .oh-card.links-category-card + .links-category-header + %h3.links-category-title= category_name + - if links.empty? + %i.links-empty= t('.no_links') + .links-list - links.each do |link| - .col-md-3.margin_bottom_25 - = h link.title - .col-md-6 - %h5.nomargin + .link-row + .link-title= h link.title + .link-url = link_to bootstrap_icon('icon-external-link', safe_slice_host(link.url)), - sanitize(link.url), target: '_blank', title: sanitize(link.url) - .col-md-2.nomargin.pull-right - - if has_permission - %a.btn.btn-mini.btn-primary{ href: edit_project_link_path(@project, link) } - %i.icon-pencil= t('edit') - - else - = disabled_button bootstrap_icon('icon-pencil', t('edit')), class: 'btn-mini btn-primary' - .clearfix - .col-md-4 - - unless links.any? && %w(Homepage Download).include?(category_name) - - new_link_title = t('new_model', model: t('links.title')) - - if has_permission - - path = new_project_link_path(@project, category_id: category_id) - %a.btn.btn-mini.btn-primary.pull-left{ href: path }= new_link_title - - else - = disabled_button new_link_title, class: 'pull-left btn-mini btn-primary' - .clearfix + sanitize(link.url), target: '_blank', rel: 'noopener', title: sanitize(link.url) + .link-actions + - if has_permission + %a.btn.btn-md.btn-primary{ href: edit_project_link_path(@project, link) } + %i.icon-pencil= t('edit') + - else + = disabled_button bootstrap_icon('icon-pencil', t('edit')), class: 'btn-md btn-primary' + - unless links.any? && %w(Homepage Download).include?(category_name) + .link-add-row + - new_link_title = t('new_model', model: t('links.title')) + - if has_permission + - path = new_project_link_path(@project, category_id: category_id) + %a.btn.btn-md.btn-primary{ href: path }= new_link_title + - else + = disabled_button new_link_title, class: 'btn-md btn-primary' -.clearfix = render partial: 'about_links' diff --git a/app/views/links/new.html.haml b/app/views/links/new.html.haml index 190f78a9d..277c2a24f 100644 --- a/app/views/links/new.html.haml +++ b/app/views/links/new.html.haml @@ -7,12 +7,10 @@ \: = link_to t('links.title').pluralize, project_links_path(@project) \: - = t('new') - = project_analysis_timestamp(@project) - + %strong= t('new') .clear -.well.col-md-8.col-md-offset-1 +.oh-card.col-md-8 = form_for([@project, @link]) do |f| = render 'fields', f: f diff --git a/app/views/logos/_about_logos.html.haml b/app/views/logos/_about_logos.html.haml index 214286038..851f944ba 100644 --- a/app/views/logos/_about_logos.html.haml +++ b/app/views/logos/_about_logos.html.haml @@ -1,12 +1,18 @@ -%h4.nomargin - %col-md.soft= t('about') - = t('.title') -.col-md-5 - %ul.margin_left_20.margin_top_10 - %li= t('.first') - %li= t('.second') - %li= t('.third') -.col-md-5 - %ul.margin_left_20.margin_top_10 - %li= t('.fourth') - %li= t('.fifth') +.about-account-basics-card + .card-header + %h4.card-title + %span.soft= t('about') + = t('.title') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + .card-content + .about-content-wrapper + .col-md-5 + %ul + %li= t('.first') + %li= t('.second') + %li= t('.third') + .col-md-5 + %ul + %li= t('.fourth') + %li= t('.fifth') diff --git a/app/views/logos/_about_organization_logos.html.haml b/app/views/logos/_about_organization_logos.html.haml index 9ad2cd48a..d52c800e0 100644 --- a/app/views/logos/_about_organization_logos.html.haml +++ b/app/views/logos/_about_organization_logos.html.haml @@ -1,11 +1,17 @@ -%h4.nomargin - %col-md.soft= t('about') - = t('.title') -.col-md-5 - %ul.margin_left_20.margin_top_10 - %li= t('.first') - %li= t('.second') - %li= t('.third') -.col-md-5 - %ul.margin_left_20.margin_top_10 - %li= t('.fourth') +.about-account-basics-card + .card-header + %h4.card-title + %span.soft= t('about') + = t('.title') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + .card-content + .about-content-wrapper + .col-md-5 + %ul + %li= t('.first') + %li= t('.second') + %li= t('.third') + .col-md-5 + %ul + %li= t('.fourth') diff --git a/app/views/logos/_fields.html.haml b/app/views/logos/_fields.html.haml index 1e3ea72f1..9a0952972 100644 --- a/app/views/logos/_fields.html.haml +++ b/app/views/logos/_fields.html.haml @@ -11,9 +11,9 @@ .col-md-10.no_margin_left{ style: 'padding: 0;' } .col-md-8.no_margin_left.ace-file-input.margin_bottom_5{ style: 'padding: 0;' } = f.file_field :attachment, class: 'new_file_upload', accept: 'image/*', - 'data_max_size' => Logo::FILE_SIZE_LIMIT.max + 'data_max_size' => Logo::FILE_SIZE_LIMIT.max, 'aria-label' => t('.head.first') %p.margin_bottom_5 - = f.text_field :url, value: @logo.url, placeholder: t('.placeholder'), class: 'col-md-5' + = f.text_field :url, value: @logo.url, placeholder: t('.placeholder'), class: 'col-md-5', 'aria-label' => t('.placeholder') - error_tag @logo, :url - error_tag @logo, :attachment_content_type if @logo.url.present? && @logo.errors[:url].blank? - error_tag @logo, :attachment_file_size @@ -47,19 +47,19 @@ .col-md-2 - unless logo.nil? %label{ for: 'logo_' + logo.id.to_s } - %img{ src: logo.attachment.url(:small) } + %img{ src: logo.attachment.url(:small), alt: label } %label{ for: 'logo_' + logo.id.to_s }= label .row.margin_top_30 .col-md-12 - .actions + .actions.margin_left_15 - if logged_in? && @parent.edit_authorized? - %input.btn.btn-sm.btn-primary{ type: 'submit', value: t('.button.save') } + = button_tag t('.button.save'), type: 'submit', class: 'btn btn-medium btn-primary' - if @parent.logo - path = @parent.is_a?(Project) ? project_logos_path(@parent) : organization_logos_path(@parent) - = link_to t('.link.blank'), path, method: :delete, class: 'btn btn-sm btn-refresh' + = link_to t('.link.blank'), path, method: :delete, class: 'btn btn-md btn-refresh' - else - = disabled_button t('.link.blank'), class: 'btn-sm btn-refresh' + = disabled_button t('.link.blank'), class: 'btn-md btn-refresh' - else - = disabled_button t('.button.save'), class: 'btn-sm btn-primary' - = disabled_button t('.link.blank'), class: 'btn-sm btn-refresh' + = disabled_button t('.button.save'), class: 'btn-md btn-primary' + = disabled_button t('.link.blank'), class: 'btn-md btn-refresh' diff --git a/app/views/logos/new.html.haml b/app/views/logos/new.html.haml index 6759f8bf8..d01997f5c 100644 --- a/app/views/logos/new.html.haml +++ b/app/views/logos/new.html.haml @@ -3,23 +3,24 @@ page_context[:select_footer_nav] = nil content_for(:html_title) { t('.page_title', name: @parent.name) } -.project_content_title - %h2.pull-left - = link_to t(:settings), settings_parent_path(@parent) - = t('.heading') - - unless @organization - = project_analysis_timestamp(@parent) +.row + .col-md-10.col-md-offset-1 + %h2 + = link_to t(:settings), settings_parent_path(@parent) + %strong= t('.heading') .row .col-md-10.col-md-offset-1 - .well + .oh-card - path = @parent.is_a?(Project) ? project_logos_path(@parent) : organization_logos_path(@parent) = form_for @logo, url: path, html: { multipart: true } do |f| = render partial: 'fields', locals: { f: f } .clearfix   -- if @project - = render partial: 'about_logos' -- elsif @organization - = render partial: 'about_organization_logos' +.row + .col-md-10.col-md-offset-1 + - if @project + = render partial: 'about_logos' + - elsif @organization + = render partial: 'about_organization_logos' diff --git a/app/views/mailers/login_changed_notification.html.haml b/app/views/mailers/login_changed_notification.html.haml index 9f4e74d7d..2c8801cb4 100644 --- a/app/views/mailers/login_changed_notification.html.haml +++ b/app/views/mailers/login_changed_notification.html.haml @@ -8,7 +8,7 @@ The old name violated new system rules about allowed characters. Your new login name is "#{@account.login}" (without the quotation marks). You may edit your login name at Open Hub's - %a{ href: 'https://www.openhub.net/accounts/me/edit' } + %a{ href: 'https://www.openhub.net/accounts/me/edit', target: '_blank', rel: 'noopener noreferrer' } Edit Account Page to any other legal value. diff --git a/app/views/managers/_about.html.haml b/app/views/managers/_about.html.haml index 049d6c786..6ba431c0e 100644 --- a/app/views/managers/_about.html.haml +++ b/app/views/managers/_about.html.haml @@ -1,21 +1,28 @@ -%h4.nomargin - %span.soft= t :about - = t(:managers_link_name) -.col-md-5 - = t('.powers_header', target_type: @parent.class) - %ul - %li= t('.powers1') - %li= t('.powers2', target_type: @parent.class.to_s.downcase) - %li= t('.powers3') - = t('.note', target_type: @parent.class.to_s.downcase) - %br - %br - = blog_link_to(link: :managing_projects, link_text: t(:learn_more)) -- if @parent.is_a?(Project) - .col-md-5 - = t('.application_header') - %ul - %li= t('.application1') - %li= t('.application2') - %li= t('.application3') - %li= t('.application4') +.about-project-basics-card + .card-header + %h4.card-title + %span.soft= t('.about') + = t(:managers_link_name) + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + + .card-content + .about-content-wrapper + .col-md-5 + = t('.powers_header', target_type: @parent.class) + %ul + %li= t('.powers1') + %li= t('.powers2', target_type: @parent.class.to_s.downcase) + %li= t('.powers3') + = t('.note', target_type: @parent.class.to_s.downcase) + %br + %br + = blog_link_to(link: :managing_projects, link_text: t(:learn_more)) + - if @parent.is_a?(Project) + .col-md-5 + = t('.application_header') + %ul + %li= t('.application1') + %li= t('.application2') + %li= t('.application3') + %li= t('.application4') diff --git a/app/views/managers/_action_buttons.html.haml b/app/views/managers/_action_buttons.html.haml index 0f42d3a80..80827061b 100644 --- a/app/views/managers/_action_buttons.html.haml +++ b/app/views/managers/_action_buttons.html.haml @@ -1,7 +1,7 @@ - if current_user_can_manage? || current_user == account - unless manage.message.blank? - %p.soft.nomargin #{t('.message_label')} #{expander(html_escape(manage.message), 20, 100)} - %p.nomargin + %p.soft #{t('managers.manage.message_label')} #{expander(html_escape(manage.message), 20, 100)} + %p - if current_user == account = edit_manages_button(@parent, account) - if manage.pending? diff --git a/app/views/managers/_form.html.haml b/app/views/managers/_form.html.haml index f7af78abb..2d7e3b935 100644 --- a/app/views/managers/_form.html.haml +++ b/app/views/managers/_form.html.haml @@ -4,18 +4,18 @@ - unless errors.empty? %p.error= errors.join('. ') -%fieldset +%fieldset.manager-form .control-group - %label.control-label= t '.message_label' + %label.control-label{ for: 'manage_message' }= t '.message_label' .controls - = find_and_preserve(form.text_area(:message, rows: 10, max_length: 200, style: 'width: 100%')) + = find_and_preserve(form.text_area(:message, rows: 10, max_length: 200, class: 'form-control')) %p.help-block .center.soft = t('.message_help1', target_type: @parent.class.to_s.downcase) %br = t('.message_help2') %br - %span= t('.promise', target: html_escape(@parent.name), target_type: @parent.class) + .center.soft= t('.promise', target: html_escape(@parent.name), target_type: @parent.class) .actions .clear{ style: 'margin-top: 5px;' } diff --git a/app/views/managers/_manage.html.haml b/app/views/managers/_manage.html.haml index 9ae3805ee..3903b9f9d 100644 --- a/app/views/managers/_manage.html.haml +++ b/app/views/managers/_manage.html.haml @@ -5,21 +5,19 @@ position = account.positions.where(project_id: @parent.id).first name_fact = position && position.name_fact - if !manage.pending? || (current_user_can_manage? || current_user == account) - .row.padding_one_top - .col-md-2 + .header_flex_container + .account_icon = avatar_for(account.person, size: 60) - .col-md-7 - %h4.soft.nomargin.no_margin_top + .manage_content + %h3 = link_to(html_escape(account.name), account_path(account)) - if manage.pending? - = t('.pending') + %span.manage-pending= t('.pending') - if position && position.title? && position.organization.present? .position %p.nomargin %span.title= position.title %span.at= t('at') %span.organization= position.organization - %p.nomargin.soft== #{name_fact ? name_fact.commits : '0'} commits + %p.soft== #{name_fact ? name_fact.commits : '0'} commits = render partial: 'action_buttons', locals: { manage: manage, account: account, name: name, target: target } - .clear -   diff --git a/app/views/managers/_new_button.html.haml b/app/views/managers/_new_button.html.haml index 66ed44d4f..eaf8bbe46 100644 --- a/app/views/managers/_new_button.html.haml +++ b/app/views/managers/_new_button.html.haml @@ -1,7 +1,7 @@ -.padding_one_top +.row.padding_one_top - if logged_in? && @organization.edit_authorized? = link_to t('.new_manager'), new_manager_organization_path(@organization), - class: 'btn btn-primary btn-small' + class: 'btn btn-primary btn-lg' - else = link_to t('.new_manager'), new_manager_organization_path(@organization), - class: "btn btn-primary btn-small #{logged_in? ? 'disabled' : 'needs_login'}" + class: "btn btn-primary btn-lg #{logged_in? ? 'disabled' : 'needs_login'}" diff --git a/app/views/managers/edit.html.haml b/app/views/managers/edit.html.haml index 4d581bb75..ff6416489 100644 --- a/app/views/managers/edit.html.haml +++ b/app/views/managers/edit.html.haml @@ -6,11 +6,10 @@ = link_to t(:managers_link_name), parent_managers_path(@parent) : = t(:edit) -= project_analysis_timestamp(@project) .clear .col-md-8 - .well + .oh-card = form_for(@manage, url: parent_manager_path(@parent, @manage), html: { method: :put }) do |form| = render partial: 'form', locals: { form: form } diff --git a/app/views/managers/index.html.haml b/app/views/managers/index.html.haml index 4d15a285c..1d766339a 100644 --- a/app/views/managers/index.html.haml +++ b/app/views/managers/index.html.haml @@ -5,31 +5,31 @@ %h2.pull-left = link_to t(:settings), settings_parent_path(@parent) : #{t(:managers_link_name)} - = project_analysis_timestamp(@project) .row - .col-md-8 - - if @manages.any? + - if @manages.any? + .col-md-8.oh-card.margin_left_15 = render partial: 'manage', collection: @manages - - else + - else + .col-md-8 = render 'shared/alert', message: t('.nobody_manages_yet') - - if @parent.is_a?(Organization) && current_user_is_admin? - %a.btn.btn-primary{ href: new_parent_manager_path(@parent), style: 'margin-left: 30px;' }= t('.new_manager') + - if @parent.is_a?(Organization) && current_user_is_admin? + %a.btn.btn-primary{ href: new_parent_manager_path(@parent), style: 'margin-left: 30px;' }= t('.new_manager') - if @parent.is_a?(Project) - .col-md-4 + .col-md-8 - unless @parent.active_managers.include?(current_user) %a.btn.btn-primary{ href: new_parent_manager_path(@parent), style: 'margin-left: 30px;' }= t('.new_cta') .clear   .padding_one_top - if !@parent.active_managers.include?(current_user) - .well + .oh-card %h5.nomargin= t('.help_header_first1') %p= t('.help_body_first1') %br %h5.nomargin= t('.help_header_first2') %p= t('.help_body_first1') - elsif !@parent.manages.pending.blank? - .well + .oh-card %h4.nomargin= t('.help_header_existing', target: html_escape(@parent.name)) %p.soft= t('.help_body_existing1') %p.soft= t('.help_body_existing2') diff --git a/app/views/managers/new.html.haml b/app/views/managers/new.html.haml index 3b52b7a49..be2442f54 100644 --- a/app/views/managers/new.html.haml +++ b/app/views/managers/new.html.haml @@ -6,18 +6,17 @@ = link_to t(:managers_link_name), parent_managers_path(@parent) : = t(:new) -= project_analysis_timestamp(@project) .clear %h4= t '.header', target: @parent.name -.col-md-7 - .well{ style: 'margin-left:22px;' } +.col-md-8 + .oh-card = form_for(@manage, url: parent_managers_path(@parent), html: { method: :post }) do |form| = render partial: 'form', locals: { form: form } .col-md-4 - .well + .oh-card %h5.nomargin= t '.explain_header' %p= @parent.active_managers.blank? ? t('.explainer_first') : t('.explainer_existing') diff --git a/app/views/oh_admin/broken_links/index.html.haml b/app/views/oh_admin/broken_links/index.html.haml index 8be37dba2..8728816e0 100644 --- a/app/views/oh_admin/broken_links/index.html.haml +++ b/app/views/oh_admin/broken_links/index.html.haml @@ -32,6 +32,6 @@ %td= link_to(h(project.name), project_links_url(project.to_param)) %td= broken_link.updated_at.strftime('%b %d, %Y') %td.text-center= button_to 'Delete', oh_admin_broken_link_path(broken_link), method: :delete, data: { confirm: t('.delete_confirmation') }, class: 'btn btn-danger btn-xs' - = will_paginate @broken_links + = render 'shared/modern_pagination', collection: @broken_links - else %h2= 'No Broken Links Found' diff --git a/app/views/oh_admin/license_permissions/index.html.haml b/app/views/oh_admin/license_permissions/index.html.haml index a421cc10c..e88e79c99 100644 --- a/app/views/oh_admin/license_permissions/index.html.haml +++ b/app/views/oh_admin/license_permissions/index.html.haml @@ -24,9 +24,9 @@ %td= license_permission.name %td= license_permission.status %td= license_permission.updated_at.strftime('%b %d, %Y') - = will_paginate @license_permissions + = render 'shared/modern_pagination', collection: @license_permissions .sidebar.col-xs-5.col-sm-6.col-lg-2 - .well + .oh-card .panel.panel-default .panel-heading.strong Filter = form_tag(oh_admin_license_permissions_path, method: 'get') do diff --git a/app/views/organization_widgets/_footer.html.haml b/app/views/organization_widgets/_footer.html.haml index fb2681fed..8d0c0e60a 100644 --- a/app/views/organization_widgets/_footer.html.haml +++ b/app/views/organization_widgets/_footer.html.haml @@ -2,4 +2,4 @@ .link_container = link_to organization_url(org), target: '_blank' do = t('.more') - = image_tag(widget_ohloh_logo_url, class: 'ohloh_logo') + = image_tag(widget_ohloh_logo_url, class: 'ohloh_logo', alt: 'Open Hub') diff --git a/app/views/organizations/_about_basics.html.haml b/app/views/organizations/_about_basics.html.haml index e441b4e0d..30003e419 100644 --- a/app/views/organizations/_about_basics.html.haml +++ b/app/views/organizations/_about_basics.html.haml @@ -1,11 +1,11 @@ -%h4.nomargin - %span.soft= t('.about') - = t('.basics') -.col-md-6 - %ul.margin_left_25.margin_top_10 - %li - = t('.info') - %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Support' }= t('.contact_us') - = t('.inapropriate') - %li= t('.url_unique') - %li= t('.description') +.about-content-wrapper + .col-md-5 + %ul + %li + = t('.info') + %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Support', target: '_blank', rel: 'noopener noreferrer' }= t('.contact_us') + = t('.inapropriate') + %li= t('.url_unique') + .col-md-5 + %ul + %li= t('.description') diff --git a/app/views/organizations/_about_managers.html.haml b/app/views/organizations/_about_managers.html.haml index fde73fe21..8f8994fb6 100644 --- a/app/views/organizations/_about_managers.html.haml +++ b/app/views/organizations/_about_managers.html.haml @@ -1,13 +1,18 @@ -.col-md-12.margin_top_20 - %h4.nomargin - %span.soft.margin_left_20= t('.about') - = t('.managers') - .col-md-6.margin_left_20 - = t('.ability') - %ul.margin_left_30 - %li= t('.limit') - %li= t('.edit') - %li= t('.approve') - %ul.margin_left_30 - %li= t('.edit_info') - +.col-md-11.about-account-basics-card + .card-header + %h4.card-title + %span.soft= t('.about') + = t('.managers') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + .card-content + .about-content-wrapper + .col-md-5 + %p= t('.ability') + %ul + %li= t('.limit') + %li= t('.edit') + %li= t('.approve') + .col-md-5 + %ul + %li= t('.edit_info') diff --git a/app/views/organizations/_fields.html.haml b/app/views/organizations/_fields.html.haml index 16759848b..de7cc813d 100644 --- a/app/views/organizations/_fields.html.haml +++ b/app/views/organizations/_fields.html.haml @@ -1,53 +1,49 @@ -.col-md-offset-1 - %fieldset - .control-group - %label.control-label.required= t('.name') +%fieldset.row-fluid + .col-md-10 + .form-group + = f.label :name, t('.name'), class: 'control-label required' .controls - = f.text_field :name, class: 'col-md-10' + = f.text_field :name, class: 'form-control' - error_tag @organization, :name - .clearfix - .control-group - %label.control-label.required= t('.url') + .form-group + = f.label :vanity_url, t('.url'), class: 'control-label required' .controls - .input-prepend - %span.add-on= 'http://www.openhub.net/orgs/' - = f.text_field :vanity_url, class: 'check-availability', - 'data-original-value' => Organization.find_by(id: @organization).try(:vanity_url), + .input-group + %span.input-group-addon= 'http://www.openhub.net/orgs/' + = f.text_field :vanity_url, class: 'form-control check-availability', + 'data-original-value' => f.object.vanity_url, 'data-ajax-path' => organization_check_availabilities_path, 'data-preview-base-url' => organizations_url, - autocomplete: 'off', style: 'width: 375px' + autocomplete: 'off' = render 'shared/availability_preview' - - error_tag @organization, :vanity_url, class: 'error vanity_url' + - error_tag @organization, :vanity_url, class: 'error vanity_url' - .control-group - %label.control-label.required= t('.type') + .form-group + = f.label :org_type, t('.type'), class: 'control-label required' .controls - Organization::ORG_TYPES.each do |label, val| %label.radio-inline = f.radio_button :org_type, val = label - %p.margin_top_15 - - error_tag @organization, :org_type, class: 'error margin_top_25' - .clearfix + - error_tag @organization, :org_type, class: 'error margin_top_25' - .control-group - %label.control-label= t('.description') + .form-group + = f.label :description, t('.description'), class: 'control-label' .controls - = find_and_preserve f.text_area(:description, max_length: 800, style: 'width: 83%', rows: '10') + = find_and_preserve f.text_area(:description, max_length: 800, class: 'form-control edit-description', rows: '10') - error_tag @organization, :description - .clearfix - .control-group - %label.control-label= t('.home') + .form-group + = f.label :homepage_url, t('.home'), class: 'control-label' .controls - = f.text_field :homepage_url, class: 'col-md-10' + = f.text_field :homepage_url, class: 'form-control' - error_tag @organization, :homepage_url - .clearfix - = f.hidden_field :id unless @organization.new_record? - .actions.pull-left.margin_top_20 - - if logged_in? && @organization.edit_authorized? - %input.btn.btn-primary{ type: 'submit', value: t('.save_changes'), 'data-disable-with' => t('.processing') } - - else - = disabled_button(t('.save_changes'), class: 'btn-primary') - .clearfix + + = f.hidden_field :id unless @organization.new_record? + + .actions.col-md-10 + - if logged_in? && @organization.edit_authorized? + = submit_tag t('.save_changes'), class: 'btn btn-primary pull-left margin_right_10', 'data-disable-with' => t('.processing') + - else + = disabled_button(t('.save_changes'), class: 'btn-primary') diff --git a/app/views/organizations/_manage_project.html.haml b/app/views/organizations/_manage_project.html.haml index 805b58a83..60cc3bbae 100644 --- a/app/views/organizations/_manage_project.html.haml +++ b/app/views/organizations/_manage_project.html.haml @@ -1,18 +1,18 @@ -%td +%td{ data: { label: t('organizations.manage_projects_header.logo') } } %a.logo.med{ href: project_path(p) } = p.decorate.icon(:med, width: 48) -%td= link_to truncate(h(p.name), length: 20), project_path(p), title: p.name -%td.center{ style: 'padding-right:15px;' }= p.user_count -%td.center{ style: 'padding-right:15px;' }= p.best_analysis.code_total -%td.left +%td{ data: { label: t('organizations.manage_projects_header.name') } }= link_to truncate(h(p.name), length: 20), project_path(p), title: p.name +%td.center{ style: 'padding-right:15px;', data: { label: t('organizations.manage_projects_header.i_use') } }= p.user_count +%td.center{ style: 'padding-right:15px;', data: { label: t('organizations.manage_projects_header.lines_of_code') } }= p.best_analysis.code_total +%td.left{ data: { label: t('organizations.manage_projects_header.activity') } } .project_activity_margin_top{ style: 'position: relative; top: -15px' } - project_activity_level_class(p, :fifteen) -%td.center{ style: 'padding-right:15px;' }= p.active_committers -%td.center +%td.center{ style: 'padding-right:15px;', data: { label: t('organizations.manage_projects_header.committers') } }= p.active_committers +%td.center{ data: { label: t('organizations.manage_projects_header.rating') } } - if p.rating_average.to_i.zero? = t('.none') - else != rating_stars('average_rating_stars', p.rating_average || 0, mini: true) -%td.center{ style: 'padding-right:15px;' }= p.reviews.count -%td.wrapword= expander(h(p.description.to_s), 100, 300) +%td.center{ style: 'padding-right:15px;', data: { label: t('organizations.manage_projects_header.reviews') } }= p.reviews.count +%td.wrapword{ data: { label: t('organizations.manage_projects_header.description') } }= expander(h(p.description.to_s), 100, 300) diff --git a/app/views/organizations/_manage_projects_header.html.haml b/app/views/organizations/_manage_projects_header.html.haml index 006245b05..1d348a791 100644 --- a/app/views/organizations/_manage_projects_header.html.haml +++ b/app/views/organizations/_manage_projects_header.html.haml @@ -1,11 +1,11 @@ %tr.manage_projects_header - %th   + %th{ scope: 'col', aria: { label: t('.logo') } } %th.col-md-3.center= t('.name') %th.col-md-1= t('.i_use') %th.center= t('.lines_of_code') - %th.col-md-1.center{ style: 'padding-left:5px;' }   + %th.col-md-1.center{ style: 'padding-left:5px;', scope: 'col', aria: { label: t('.activity') } } %th.col-md-1.center= t('.committers') %th.col-md-1.center= t('.rating') %th.col-md-1.center= t('.reviews') %th.col-md-2= t('.description') - %th   + %th{ scope: 'col', aria: { label: t('.actions') } } diff --git a/app/views/organizations/_organization.html.haml b/app/views/organizations/_organization.html.haml index 5b2cb11f1..80e963a80 100644 --- a/app/views/organizations/_organization.html.haml +++ b/app/views/organizations/_organization.html.haml @@ -1,15 +1,33 @@ -.well.searchable{ id: "organization_#{organization.id}", style: 'padding-left: 70px;' } - .row.no_padding_top - .col-md-7{ style: 'margin-top: -15px; font-size:16px;' } - %h3= link_to h(organization.name), organization_path(organization) - .row - .col-md-1 - = link_to organization.decorate.icon(:med), organization_path(organization) - .col-md-9.margin_left_10 - %p{ style: 'word-wrap: break-word' }= expander(h(organization.description), 300, 350) - .row - .col-md-9{ style: 'margin-left: 85px' } - = link_to pluralize_with_delimiter(organization.projects_count, 'projects'), - projects_organization_path(organization) - %span.seperator  |  - = pluralize_with_delimiter(organization.affiliators_count, 'affiliated committer') +.organization-card.searchable{ id: "organization_#{organization.id}" } + / Organization Header + .organization-header + .organization-title-section + %h2 + = link_to h(organization.name.truncate(70)), organization_path(organization), title: organization.name + + / Organization Content + .organization-content + .organization-main + .organization-icon-desc + = link_to organization.decorate.icon(:med), organization_path(organization) + + .organization-description + - description = organization.description.to_s.strip + - if description.size > 220 + = expander(h(description), 220, 300) + - elsif description.present? + = h(description) + - else + %span.text-muted= t('organizations.show.no_desc') + + .organization-stats + .stat-item + %span.stat-value + - if organization.projects_count > 0 + = link_to number_with_delimiter(organization.projects_count), projects_organization_path(organization) + - else + = number_with_delimiter(organization.projects_count) + %span.stat-label= t('.projects') + .stat-item + %span.stat-value= number_with_delimiter(organization.affiliators_count) + %span.stat-label= t('.affiliated_committers') diff --git a/app/views/organizations/_pagination.html.haml b/app/views/organizations/_pagination.html.haml index 0e47d528f..24c52ac4b 100644 --- a/app/views/organizations/_pagination.html.haml +++ b/app/views/organizations/_pagination.html.haml @@ -1,7 +1,8 @@ - if request.xhr? || action_name == 'show' - if count.to_i > 10 - = link_to(h(text), h(url), class: 'btn btn-mini btn-primary') + .pagination-button-container + = link_to(h(text), h(url), class: 'btn-see-all-org') - else %p   - else - = will_paginate collection + = render 'shared/modern_pagination', collection: collection diff --git a/app/views/organizations/claim_projects_list.html.haml b/app/views/organizations/claim_projects_list.html.haml index 2724f73cd..1fab3b2eb 100644 --- a/app/views/organizations/claim_projects_list.html.haml +++ b/app/views/organizations/claim_projects_list.html.haml @@ -35,7 +35,7 @@ title: t('.link_title', org: h(p.organization.name.to_s)), rel: 'tooltip' } %i.icon-warning-sign = t('.report_text') - = will_paginate @projects + = render 'shared/modern_pagination', collection: @projects .clearfix   .margin_top_10 diff --git a/app/views/organizations/edit.html.haml b/app/views/organizations/edit.html.haml index 674718f11..8e74d6fad 100644 --- a/app/views/organizations/edit.html.haml +++ b/app/views/organizations/edit.html.haml @@ -3,15 +3,21 @@ page_context[:select_top_menu_nav] = 'select_organizations' page_context[:select_footer_nav] = nil -%h2.pull-left - = link_to t('.settings'), settings_organization_path(@organization) - = t('.basics') -.clearfix +#accounts_edits + .margin_left_15 + %h2.margin_left_15 + = link_to t('.settings'), settings_organization_path(@organization) + %span.account-basics-title= t('.basics') + .oh-card.margin_left_15#account-edit-form + = form_for @current_object, url: organization_path(@organization) do |f| + = render partial: 'fields', locals: { f: f } -.row - .well.col-md-10.col-md-offset-1 - = form_for @current_object, url: organization_path(@organization), - class: 'center-block', style: 'width:650px' do |f| - = render partial: 'fields', locals: { f: f } - -= render partial: 'about_basics' +.about-account-basics-card.margin_left_15 + .card-header + %h4.card-title + %span.soft= t('about') + = t('basics') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + .card-content + = render partial: 'about_basics' diff --git a/app/views/organizations/index.html.haml b/app/views/organizations/index.html.haml index 79ce2e1e8..5308bbea8 100644 --- a/app/views/organizations/index.html.haml +++ b/app/views/organizations/index.html.haml @@ -1,17 +1,29 @@ - content_for(:html_title) { t('.page_title') } - page_context[:select_top_menu_nav] = 'select_organizations' -.row - .col-md-2.pull-left - %h1= t('.title') - - if current_user_is_admin? - .col-md-3.pull-right - = link_to t('.add'), new_organization_path, class: 'btn btn-small btn-info margin_top_20 margin_left_15' +#organizations_index_page + .organizations-container + / Header Section with Actions + .organizations-header + %h1= t('.title') + .header-actions + - if current_user_is_admin? + = link_to new_organization_path, class: 'btn-add-organization' do + %i.fa.fa-plus + = t('.add') -= render partial: 'shared/search_dingus', locals: { collection: @organizations, sort_context: :organizations } + / Header Search with Dropdown + = render 'shared/search' -- if @organizations.empty? - = t('.no_matches') -- else - = render partial: 'organization', collection: @organizations - = will_paginate @organizations + / Search and Filter Bar + = render partial: 'shared/search_dingus', locals: { collection: @organizations, sort_context: :organizations } + + / Organizations List + #organizations_index_list + - if @organizations.empty? + .no-results= t('.no_matches') + - else + = render partial: 'organization', collection: @organizations + + / Modern Pagination + = render 'shared/modern_pagination', collection: @organizations diff --git a/app/views/organizations/list_managers.html.haml b/app/views/organizations/list_managers.html.haml index a16bd07df..4634c3d44 100644 --- a/app/views/organizations/list_managers.html.haml +++ b/app/views/organizations/list_managers.html.haml @@ -3,11 +3,12 @@ page_context[:select_footer_nav] = nil content_for(:html_title) { t('.page_title', name: @organization.name) } -%h2.pull-left - = link_to t('.settings'), settings_organization_path(@organization) - \: - = t('.managers') -.clearfix +.row + .col-md-offset-1 + %h2 + = link_to t('.settings'), settings_organization_path(@organization) + \: + %strong= t('.managers') - if @managers.empty? = render partial: 'shared/alert', locals: { message: t('.empty_alert') } @@ -19,7 +20,7 @@ .col-md-11 - @managers.each do |manager| .row.padding_one_top - .col-md-11 + .col-md-11.oh-card .pull-left = avatar_for(manager.person, size: 60) .col-md-9.pull-left @@ -31,10 +32,12 @@ = link_to link_values[:path], link_values[:options] do %i.icon-trash= t('.remove').titleize - else - = link_to '#', class: "btn btn-mini btn-danger #{logged_in? ? 'disabled' : 'needs_login'}" do + = link_to '#', class: "btn btn-medium btn-danger #{logged_in? ? 'disabled' : 'needs_login'}" do %i.icon-trash= t('.remove') .clearfix   = render partial: 'managers/new_button' -= render partial: 'about_managers' +.row + .col-md-offset-1 + = render partial: 'about_managers' diff --git a/app/views/organizations/manage_projects.html.haml b/app/views/organizations/manage_projects.html.haml index 693feb397..eb94d1582 100644 --- a/app/views/organizations/manage_projects.html.haml +++ b/app/views/organizations/manage_projects.html.haml @@ -9,19 +9,20 @@ .col-md-12 = render 'shared/search_dingus', collection: @projects, sort_context: :claim_projects_list, no_match_found_type: :none - %table.table.table-striped.table-condensed - %thead - = render partial: 'manage_projects_header' - %tbody - - @projects.each do |p| - %tr - = render partial: 'manage_project', locals: { p: p } - %td.center - - if logged_in? && @organization.edit_authorized? - = render partial: 'active_remove_project_button', locals: { p: p } - - else - = disabled_button(bootstrap_icon('icon-trash', t('.remove')), class: 'btn-danger btn-mini') - = will_paginate @projects + .table-responsive + %table.table.table-striped.table-condensed.manage-projects-table + %thead + = render partial: 'manage_projects_header' + %tbody + - @projects.each do |p| + %tr + = render partial: 'manage_project', locals: { p: p } + %td.center{ data: { label: t('.actions') } } + - if logged_in? && @organization.edit_authorized? + = render partial: 'active_remove_project_button', locals: { p: p } + - else + = disabled_button(bootstrap_icon('icon-trash', t('.remove')), class: 'btn-danger btn-mini') + = render 'shared/modern_pagination', collection: @projects .clearfix diff --git a/app/views/organizations/new.html.haml b/app/views/organizations/new.html.haml index 70fd7751b..644b92c4e 100644 --- a/app/views/organizations/new.html.haml +++ b/app/views/organizations/new.html.haml @@ -1,11 +1,13 @@ - content_for(:html_title) { t('.page_title') } - page_context[:select_top_menu_nav] = 'select_organizations' -%h2= t('.title') +.row + .col-md-10.col-md-offset-1 + %h2.org-new-title= t('.title') .clearfix .row - .well.col-md-10.col-md-offset-1 + .oh-card.col-md-10.col-md-offset-1.org-form = form_for @organization, url: organizations_path, class: 'center-block' do |f| = render partial: 'fields', locals: { f: f } diff --git a/app/views/organizations/new_manager.html.haml b/app/views/organizations/new_manager.html.haml index 62d53cea7..69c02ff1b 100644 --- a/app/views/organizations/new_manager.html.haml +++ b/app/views/organizations/new_manager.html.haml @@ -1,15 +1,16 @@ - page_context[:select_footer_nav] = nil - content_for(:html_title) { t('.page_title', name: @organization.name) } -%h2.pull-left - = link_to t('.settings'), settings_organization_path(@organization) - \: - = link_to t('.managers'), list_managers_organization_path(@organization) - = t('.new') -.clearfix +.row + .col-md-offset-1 + %h2 + = link_to t('.settings'), settings_organization_path(@organization) + \: + = link_to t('.managers'), list_managers_organization_path(@organization) + %strong= t('.new') .row.padding_one_top - .well.col-md-8.col-md-offset-2 + .oh-card.col-md-8.col-md-offset-1#account-edit-form = form_for @manage, url: new_manager_organization_path(@organization), class: 'nomargin' do |_| - unless @manage.errors.empty? - errors = @manage.errors.full_messages @@ -18,14 +19,13 @@ %p.error= errors.join('. ') %fieldset - .control-group - %label.control-label.required= t('.new_manager') + .form-group + %label.control-label.required{ for: 'org_new_manager' }= t('.new_manager') .controls = text_field_tag :account_name, params[:account_name], - id: 'org_new_manager', class: 'autocompletable col-md-7', placeholder: t('.enter_name'), + id: 'org_new_manager', class: 'autocompletable form-control', placeholder: t('.enter_name'), data: { source: '/autocompletes/account', select: 'new_manager' } = hidden_field_tag :account_id - .clearfix .actions.margin_top_15 - if logged_in? && @organization.edit_authorized? %input.btn.btn-primary{ type: 'submit', value: t('.add'), id: 'org_new_manager_submit' } diff --git a/app/views/organizations/settings.html.haml b/app/views/organizations/settings.html.haml index 3e02fa4c3..083394af8 100644 --- a/app/views/organizations/settings.html.haml +++ b/app/views/organizations/settings.html.haml @@ -3,45 +3,52 @@ page_context[:select_top_menu_nav] = 'select_organizations' page_context[:select_footer_nav] = :settings -%h2.pull-left= t('settings') -.clearfix +.settings-page-header + .settings-page-header-row + .settings-page-icon + %i.fa.fa-cog + %h2.settings-page-h2= t('.title') + %p.settings-page-description= t('.configure_desc', name: @organization.name) -.row.margin_bottom_20 - .col-md-4.settings_module - %h4.nomargin - = link_to t('.basics'), edit_organization_path(@organization) - %span.pull-left - %a.module_icon.basics{ href: edit_organization_path(@organization) } - %span.pull-left.settings_description= t('.name_description_and_url') - .col-md-4.settings_module - %h4.nomargin - = link_to t('managers_link_name'), list_managers_organization_path(@organization) - %span.pull-left - %a.module_icon.managers{ href: list_managers_organization_path(@organization) } - %span.pull-left.settings_description= t('.users_who_manage') - .col-md-4.settings_module - %h4.nomargin - = link_to t('.edit_history'), organization_edits_path(@organization) - %span.pull-left - %a.module_icon.history{ href: organization_edits_path(@organization) } - %span.pull-left.settings_description= t('.list_of_recent_activity') +.settings-grid + %a.settings_module{ href: edit_organization_path(@organization) } + .settings-icon-wrapper.gradient-purple + %i.fa.fa-building + .settings-text + %h4= t('.basics') + %span.settings_description= t('.name_description_and_url') -.row.margin_bottom_20 - .col-md-4.settings_module - %h4.nomargin - = link_to t('logo'), new_organization_logos_path(@organization) - %span.pull-left - %a.module_icon.logo{ href: new_organization_logos_path(@organization) } - %span.pull-left.settings_description= t('.logo_displayed_for_org') - .col-md-4.settings_module - %h4.nomargin - = link_to t('.manage_projects'), manage_projects_organization_path(@organization) - %span.pull-left - %a.module_icon.orgs_manage_projects{ href: manage_projects_organization_path(@organization) } - %span.pull-left.settings_description= t('.held_projects') - .col-md-4.settings_module - %h4.nomargin - = link_to t('.permissions'), permissions_organization_path(@organization) - %span.pull-left - %a.module_icon.permissions{ href: permissions_organization_path(@organization) } - %span.pull-left.settings_description= t('.permissions_desc') + %a.settings_module{ href: list_managers_organization_path(@organization) } + .settings-icon-wrapper.gradient-sky + %i.fa.fa-user-md + .settings-text + %h4= t('managers_link_name') + %span.settings_description= t('.users_who_manage') + + %a.settings_module{ href: organization_edits_path(@organization) } + .settings-icon-wrapper.gradient-violet + %i.fa.fa-history + .settings-text + %h4= t('.edit_history') + %span.settings_description= t('.list_of_recent_activity') + + %a.settings_module{ href: new_organization_logos_path(@organization) } + .settings-icon-wrapper.gradient-pink + %i.fa.fa-picture-o + .settings-text + %h4= t('logo') + %span.settings_description= t('.logo_displayed_for_org') + + %a.settings_module{ href: manage_projects_organization_path(@organization) } + .settings-icon-wrapper.gradient-orange + %i.fa.fa-briefcase + .settings-text + %h4= t('.manage_projects') + %span.settings_description= t('.held_projects') + + %a.settings_module{ href: permissions_organization_path(@organization) } + .settings-icon-wrapper.gradient-red + %i.fa.fa-lock + .settings-text + %h4= t('.permissions') + %span.settings_description= t('.permissions_desc') diff --git a/app/views/organizations/show.html.haml b/app/views/organizations/show.html.haml index 02c7e33f3..df37f6009 100644 --- a/app/views/organizations/show.html.haml +++ b/app/views/organizations/show.html.haml @@ -1,27 +1,32 @@ - page_context[:select_top_menu_nav] = 'select_organizations' - content_for(:html_title) { t('.page_title', organization_name: @organization.name) } +.org-summary-container + .org-summary-grid + .org-summary-box + %h4= t('.title') + #org_summary + - if @organization.description.blank? + = t('.no_desc') + = link_to t('.add_desc'), edit_organization_path(@organization) + - else + = simple_format @organization.description -%h2= t('.title') -.col-md-5.pull_left#org_summary - - if @organization.description.blank? - = t('.no_desc') - = link_to t('.add_desc'), edit_organization_path(@organization) - - else - = simple_format @organization.description.strip_tags - = render partial: '/shared/add_this', locals: { text: "#{@organization.name} organization" } -.col-md-6.col-md-offset-1 - = render partial: 'organizations/show/quick_reference' + = render partial: 'organizations/show/quick_reference' -.col-md-12 - .mezzo.padding_one_top - = render partial: '/organizations/show/pictogram', locals: { print_icon: true } - .mezzo.padding_one_top - .org-data-table.margin_left_10.margin_right_10 + .org-content-container + .org-page-section + = render partial: '/shared/add_this', locals: { text: "#{@organization.name} organization" } + + .org-page-section + = render partial: '/organizations/show/pictogram', locals: { print_icon: true } + + .org-page-section + .org-data-table - case @view - when :affiliated_committers = render partial: 'organizations/show/affiliated_committers', - affiliated_committers: @affiliated_committers, stats_map: @stats_map + affiliated_committers: @affiliated_committers, stats_map: @stats_map - when :portfolio_projects = render partial: 'organizations/show/portfolio_projects', affiliated_projects: @affiliated_projects - when :outside_committers @@ -29,7 +34,7 @@ - when :outside_projects = render partial: 'organizations/show/outside_projects', projects: @projects -.spinner.hidden - %h4 - %img{ src: image_path('spinner.gif') } - = t('.loading') + .spinner.hidden + %h4 + %img{ src: image_path('spinner.gif'), alt: '' } + = t('.loading') diff --git a/app/views/organizations/show/_affiliated_committers.html.haml b/app/views/organizations/show/_affiliated_committers.html.haml index 3fbcef1a5..bef4bea7c 100644 --- a/app/views/organizations/show/_affiliated_committers.html.haml +++ b/app/views/organizations/show/_affiliated_committers.html.haml @@ -2,22 +2,80 @@ - if @affiliated_committers.empty? %p= t('organizations.affiliated_committers.no_committers') - else - %table.table.table-striped.table-condensed#orgs_affiliated_list + -# Mobile card view + .affiliated-committers-mobile-view + - @affiliated_committers.each do |a| + - stats = organization_affiliated_committers_stats(@stats_map[a.id]) + - next unless stats + - most_committed_project = stats[:most_committed_project] + - most_recent_project = stats[:most_recent_project] + - account_org = Account::OrganizationCore.new(a) + + .committer-card + .card-header + .committer-info + %a{ href: account_path(a) } + = image_tag avatar_img_path(a, 48), size: '48x48', class: 'committer-avatar', alt: h(a.name) + .committer-details + %a.committer-name{ href: account_path(a), title: h(a.name) } + = a.name.truncate(25) + - if @current_user && @current_user.id == a.id + %span.you-badge= t('organizations.affiliated_committers.you') + .committer-badges + .badge-item + %span.badge-label= t('organizations.affiliated_committers.kudos') + != avatar_small_laurels(a.person.kudo_rank) + .badge-item + %span.badge-label= t('organizations.affiliated_committers.level') + .mini-badges-section + = render 'accounts/badges_row', + badges: [BadgeDecorator.new(Badge::FosserBadge.new(a, positions_count: a.positions.count))], + hide_foss_logo: true + + .card-stats + .stat-group + .stat-label= t('organizations.affiliated_committers.projects_committed_to') + .stat-values + .stat-item + %span.stat-sublabel= "#{t('organizations.affiliated_committers.portfolio')}:" + %span.stat-value= (account_org.contributions_to_org_portfolio.to_i == 0) ? '—' : account_org.contributions_to_org_portfolio.to_i + .stat-item + %span.stat-sublabel= "#{t('organizations.affiliated_committers.other')}:" + %span.stat-value= (account_org.contributions_outside_org.to_i == 0) ? '—' : account_org.contributions_outside_org.to_i + + .stat-group + .stat-label= t('organizations.affiliated_committers.most_commits_to') + .stat-project-info + %a.project-icon{ href: project_path(most_committed_project), title: most_committed_project.name } + = most_committed_project.decorate.icon(:small) + .project-details + %a.project-name{ href: project_path(most_committed_project) }= truncate(most_committed_project.name, length: 25) + %span.commit-count= pluralize(stats[:max_commits], t('organizations.affiliated_committers.commit')) + + .stat-group + .stat-label= t('organizations.affiliated_committers.most_recent_commits_on') + .stat-project-info + %a.project-icon{ href: project_path(most_recent_project), title: most_recent_project.name } + = most_recent_project.decorate.icon(:small) + .project-details + %a.project-name{ href: project_path(most_recent_project) }= truncate(most_recent_project.name, length: 25) + %span.commit-date= stats[:last_checkin].to_date.to_s(:by) if stats[:last_checkin] + + -# Desktop table view + %table.table.table-striped.table-condensed.affiliated-committers-desktop-view#orgs_affiliated_list %thead - %tr{ id: 'dingus-row' } - %td{ colspan: 14 }   %tr %th{ rowspan: 2, style: 'width: 170px;' }= t('organizations.affiliated_committers.contrib_name') %th.center{ rowspan: 2, style: 'width: 80px;' }= t('organizations.affiliated_committers.kudos') %th{ rowspan: 2, style: 'width: 96px;' } - = image_tag('icons/foss-no-bg.png') + = image_tag('icons/foss-no-bg.png', alt: t('organizations.affiliated_committers.level')) = t('organizations.affiliated_committers.level') %th.center{ colspan: 2, style: 'width:200px' } %strong= t('organizations.affiliated_committers.projects_committed_to') - %th{ rowspan: 2, style: 'width: 20px;' }   + %th{ rowspan: 2, style: 'width: 20px;', aria: { hidden: 'true' } } %th.center{ colspan: 2, style: 'width:200px' } %strong= t('organizations.affiliated_committers.most_commits_to') - %th{ rowspan: 2, style: 'width: 20px;' }   + %th{ rowspan: 2, style: 'width: 20px;', aria: { hidden: 'true' } } %th.center{ colspan: 2, style: 'width:200px' } %strong= t('organizations.affiliated_committers.most_recent_commits_on') %tr @@ -37,8 +95,8 @@ - account_org = Account::OrganizationCore.new(a) %tr %td - %a.float_left{ href: account_path(a) } - = image_tag avatar_img_path(a, 32), size: '32x32', style: 'margin-right:8px;' + %a.float_left{ href: account_path(a), title: h(a.name) } + = image_tag avatar_img_path(a, 32), size: '32x32', style: 'margin-right:8px;', alt: h(a.name) %a{ href: account_path(a), title: h(a.name) } = a.name.truncate(20) - if @current_user && @current_user.id == a.id @@ -71,4 +129,3 @@ url: affiliated_committers_organization_path(@organization), collection: @affiliated_committers } -.clear   diff --git a/app/views/organizations/show/_header.html.haml b/app/views/organizations/show/_header.html.haml index ce0d35a3b..9718fb4a8 100644 --- a/app/views/organizations/show/_header.html.haml +++ b/app/views/organizations/show/_header.html.haml @@ -1,14 +1,27 @@ -.col-md-1.no_padding#org_icon - = @organization.decorate.icon(:med) -.col-md-11#project_header - .pull-left - %h1.margin_top_10= link_to h(@organization.name), organization_path(@organization) - .pull-left.margin_left_25.margin_top_15 - = link_to settings_organization_path(@organization) do - %icon.icon-cogs - = t('.settings') +.org-header-banner + .org-header-container + .org-header-content + .org-header-logo-section + = @organization.decorate.icon(:med) + + .org-header-info + .org-header-title + %h1= link_to h(@organization.name), organization_path(@organization) + + .org-header-stats + - if @organization.projects_count + %span.stat-item + %icon.icon-code-fork + = pluralize(@organization.projects_count, t('.portfolio_project')) + - if @organization.affiliators_count + %span.stat-item + %icon.icon-user + = pluralize(@organization.affiliators_count, t('.affiliated_committer')) + + .org-header-actions + = link_to settings_organization_path(@organization), class: 'org-header-settings-btn' do + %icon.icon-cogs + = t('.settings') .clearfix   -.col-md-12 - .mezzo = render partial: 'layouts/partials/alert' diff --git a/app/views/organizations/show/_outside_committers.html.haml b/app/views/organizations/show/_outside_committers.html.haml index 82cd152de..537fb56ea 100644 --- a/app/views/organizations/show/_outside_committers.html.haml +++ b/app/views/organizations/show/_outside_committers.html.haml @@ -2,19 +2,69 @@ - if @outside_committers.empty? %p= t('organizations.outside_committers.no_committers') - else - %table.table.table-striped.table-condensed#outside_committers_list + -# Preload all projects to avoid N+1 queries + - project_ids = @outside_committers.flat_map { |account| Array(account.projs) }.compact.uniq + - projects_by_id = Project.where(id: project_ids).index_by(&:id) + + -# Mobile card view + .outside-committers-mobile-view + - @outside_committers.each do |account| + - projects = Array(account.projs).map { |project_id| projects_by_id[project_id] }.compact + + .committer-card + .card-header + .committer-info + %a{ href: account_path(account) } + = image_tag avatar_img_path(account, 48), size: '48x48', class: 'committer-avatar', alt: h(account.name) + .committer-details + %a.committer-name{ href: account_path(account), title: h(account.name) } + = truncate(account.name, length: 25) + - if current_user.id == account.id + %span.you-badge= t('organizations.outside_committers.you') + .committer-badges + .badge-item + %span.badge-label= t('organizations.outside_committers.kudos') + != avatar_small_laurels(account.person.kudo_rank) + .badge-item + %span.badge-label= t('organizations.outside_committers.level') + .mini-badges-section + = render 'accounts/badges_row', + badges: [Badge::FosserBadge.new(account, positions_count: account.positions.count)], + hide_foss_logo: true + + .card-stats + .stat-group + .stat-label= t('organizations.outside_committers.affiliated_with') + .stat-value + - if account.organization_id.nil? + = t('organizations.outside_committers.unaffiliated') + - else + %a{ href: organization_path(account.organization) }= h(account.organization.name) + + .stat-group + .stat-label= t('organizations.outside_committers.contribution_to_portfolio') + .stat-subgroup + .stat-item + %span.stat-sublabel= t('organizations.outside_committers.projects') + .project-list + - project_links = projects.map { |p| link_to h(truncate(p.name, length: 30)), project_path(p), title: p.name, class: 'project-link' } + != expander(project_links.join(', '), 100, 250, /<\/a>/, 3) + .stat-item + %span.stat-sublabel= t('organizations.outside_committers.twelve_month_commits') + %span.stat-value= account.twelve_mo_commits + + -# Desktop table view + %table.table.table-striped.table-condensed.outside-committers-desktop-view#outside_committers_list %thead - %tr{ id: 'dingus-row' } - %td{ colspan: '10' }   %tr %th{ rowspan: 2, style: 'width: 160px;' }= t('.contributor_name') %th.center{ rowspan: 2, style: 'width: 70px;' }= t('.kudos') %th.foss_level_title{ rowspan: 2, style: 'width: 85px;' } - = image_tag('icons/foss-no-bg.png') + = image_tag('icons/foss-no-bg.png', alt: t('organizations.outside_committers.level')) = t('organizations.outside_committers.level') %th.center{ rowspan: 2, style: 'width:150px' } %strong= t('organizations.outside_committers.affiliated_with') - %th{ rowspan: 2, style: 'width: 20px;' }   + %th{ rowspan: 2, style: 'width: 20px;', aria: { hidden: 'true' } } %th.center{ colspan: 2 } %strong= t('organizations.outside_committers.contribution_to_portfolio') %tr @@ -23,15 +73,15 @@ %tbody - @outside_committers.each do |account| - - projects = Project.where(id: account.projs) + - projects = Array(account.projs).map { |project_id| projects_by_id[project_id] }.compact %tr %td.name - %a.pull-left{ href: account_path(account) } - = image_tag avatar_img_path(account, 32), size: '32x32', style: 'margin-right:8px;' + %a.pull-left{ href: account_path(account), title: h(account.name) } + = image_tag avatar_img_path(account, 32), size: '32x32', style: 'margin-right:8px;', alt: h(account.name) %a{ href: account_path(account), title: h(account.name) } = truncate(account.name, length: 20) - if current_user.id == account.id - %span (You) + %span= t('organizations.outside_committers.you') %td.kudo_rank_column.center!= avatar_small_laurels(account.person.kudo_rank) %td.center .mini-badges-section{ style: 'margin:0; position:relative;height: 0px' } diff --git a/app/views/organizations/show/_outside_projects.html.haml b/app/views/organizations/show/_outside_projects.html.haml index 50b8796f1..6f61ccc6d 100644 --- a/app/views/organizations/show/_outside_projects.html.haml +++ b/app/views/organizations/show/_outside_projects.html.haml @@ -2,13 +2,55 @@ %p= t('organizations.outside_projects.no_outside_projects') - else %h3= t('organizations.outside_projects.outside_projects') - %table.table.table-striped.table-condensed + + -# Mobile card view + .outside-projects-mobile-view + - @outside_projects.each do |project| + .outside-project-card + .card-header + .project-info + = link_to h(truncate(project.name, length: 30)), project_path(project), title: project.name, class: 'project-name' + .project-meta + %a.twenty_project_activity_level{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-About-Project-Activity-Icons', + target: '_blank', + rel: 'noopener noreferrer', + class: "twenty_project_activity_level_#{project.best_analysis.activity_level}", + title: project_activity_text(project, true) } +   + - if project.rating_average && project.rating_average > 0 + %span.org_project_rating + != rating_stars('rating_star', project.rating_average, mini: true) + + .card-stats + .stat-box + .stat-item + %span.stat-label= t('organizations.outside_projects.header_claimed_by') + %span.stat-value + - if project.organization + %a{ href: organization_path(project.organization) }= h(project.organization.name) + - else + = '—' + + .stat-box + .stat-item + %span.stat-label= t('organizations.outside_projects.header_i_use_this') + %span.stat-value= project.user_count + .stat-item + %span.stat-label= t('organizations.outside_projects.header_num_of_affiliates') + %span.stat-value= project.contribs_count + + .stat-box + .stat-item + %span.stat-label= t('organizations.outside_projects.header_all_time_commits') + %span.stat-sublabel= t('organizations.outside_projects.header_by_current_affiliates') + %span.stat-value= project.commits + + -# Desktop table view + %table.table.table-striped.table-condensed.outside-projects-desktop-view %thead - %tr{ id: 'dingus-row' } - %td{ colspan: '10' }   %tr %th{ style: 'width: 190px;' }= t('organizations.outside_projects.header_name') - %th.center{ style: 'width: 50px;' }   + %th.center{ style: 'width: 50px;', scope: 'col', aria: { label: t('organizations.outside_projects.header_activity') } } %th.center{ style: 'width: 150px;' }= t('organizations.outside_projects.header_claimed_by') %th.center{ style: 'width: 90px;' }= t('organizations.outside_projects.header_i_use_this') %th.center{ style: 'width: 110px;' }= t('organizations.outside_projects.header_community_rating') diff --git a/app/views/organizations/show/_pictogram.html.haml b/app/views/organizations/show/_pictogram.html.haml index 41b7b3558..bd82620ed 100644 --- a/app/views/organizations/show/_pictogram.html.haml +++ b/app/views/organizations/show/_pictogram.html.haml @@ -1,40 +1,49 @@ - partial_dir = 'organizations/show/pictogram' - print_path = print_infographic_organization_path(@organization) -.margin_left_15.margin_right_15#org_infographic - #section1 - .col1.pull-left.strong - = render partial: "#{partial_dir}/outside_committers", locals: @graphics.outside_committers - .col2.pull-left{ @graphics.other_attributes('out_commits', positioning: true) } - .svg-arrow.margin_bottom_10{ @graphics.arrow_attributes('out_commits', 'right') } - = render partial: "#{partial_dir}/outside_committers_commits", locals: @graphics.outside_committers - #infographic_box{ default_view: @view } - .header= @organization.name - .content - .col1.center.pull-left - = render partial: "#{partial_dir}/portfolio_projects", locals: @graphics.portfolio_projects - .col2.center.pull-left - .svg-arrow.margin_bottom_10{ @graphics.arrow_attributes('affl_commits', 'left', 130) } +#org_infographic + .infographic-header= @organization.name + + .infographic-main + #section1 + .col1 + = render partial: "#{partial_dir}/outside_committers", locals: @graphics.outside_committers + .commits-text + = render partial: "#{partial_dir}/outside_committers_commits", locals: @graphics.outside_committers + .col2{ @graphics.other_attributes('out_commits', positioning: true) } + .svg-arrow{ @graphics.arrow_attributes('out_commits', 'right') } + + + #infographic_box{ default_view: @view } + .content + .col1 + = render partial: "#{partial_dir}/portfolio_projects", locals: @graphics.portfolio_projects + .col2 + .svg-arrow{ @graphics.arrow_attributes('affl_commits', 'left', 130) } + .col3 + = render partial: "#{partial_dir}/affiliated_committers", locals: @graphics.portfolio_commits + .portfolio-stats = render partial: "#{partial_dir}/portfolio_commits", locals: @graphics.portfolio_commits - .col3.pull-left - = render partial: "#{partial_dir}/affiliated_committers", locals: @graphics.portfolio_commits - .footer_line   - .bottom_links - .pull-left.margin_top_5.col1 - = render 'shared/creativecommons_license' - .pull-left.center.col2 - %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Unveiling-the-Retooled-Organization', target: '_blank' } - = t('organizations.show.about_graphic') - .col3.pull-right - .pull-right.about Black Duck | OpenHub - %br - - if print_icon - %a.pull-right.small.margin_top_5.print_infographic{ href: '#', url: print_path } - %i.icon-print - = t('organizations.show.print_graphic') - #section2 - .col1.pull-left{ @graphics.other_attributes('affl_commits_out', positioning: true) } - .svg-arrow.margin_bottom_10{ @graphics.arrow_attributes('affl_commits_out', 'right', 140) } - = render partial: "#{partial_dir}/outside_project_commits", locals: @graphics.outside_project_commits - .col2.pull-left.strong - = render partial: "#{partial_dir}/outside_projects", locals: @graphics.outside_project_commits + .footer_line + + #section2 + .col1{ @graphics.other_attributes('affl_commits_out', positioning: true) } + .svg-arrow{ @graphics.arrow_attributes('affl_commits_out', 'right', 140) } + + .col2 + = render partial: "#{partial_dir}/outside_projects", locals: @graphics.outside_project_commits + .commits-text + = render partial: "#{partial_dir}/outside_project_commits", locals: @graphics.outside_project_commits + + .infographic-footer + .footer-col1 + = render 'shared/creativecommons_license' + .footer-col2 + %a{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Unveiling-the-Retooled-Organization', target: '_blank', rel: 'noopener noreferrer' } + = t('organizations.show.about_graphic') + .footer-col3 + .about Black Duck | OpenHub + - if print_icon + %a.print_infographic{ href: '#', url: print_path } + %i.icon-print + = t('organizations.show.print_graphic') diff --git a/app/views/organizations/show/_portfolio_projects.html.haml b/app/views/organizations/show/_portfolio_projects.html.haml index 9bedc8703..5b525e023 100644 --- a/app/views/organizations/show/_portfolio_projects.html.haml +++ b/app/views/organizations/show/_portfolio_projects.html.haml @@ -1,16 +1,81 @@ - if @affiliated_projects.any? %h3= t('organizations.projects.title') - %table.table.table-striped.table-condensed + + -# Mobile card view + .portfolio-mobile-view + - @affiliated_projects.each do |project| + - best_analysis = project.best_analysis + - twelve_month_summary = best_analysis.twelve_month_summary + - previous_twelve_month_summary = best_analysis.previous_twelve_month_summary + - language = best_analysis.main_language + + - best_analysis_decorator = AnalysisDecorator.new(best_analysis) + - affiliated_commits_diff = best_analysis_decorator.affiliated_commits_difference + - affiliated_committers_diff = best_analysis_decorator.affiliated_committers_difference + - outside_commits_diff = best_analysis_decorator.outside_commits_difference + - outside_committers_diff = best_analysis_decorator.outside_committers_difference + + .portfolio-project-card + .card-header + .project-info + = link_to h(truncate(project.name, length: 30)), project_path(project), title: project.name, class: 'project-name' + .project-meta + %a.twenty_project_activity_level{ href: 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-About-Project-Activity-Icons', + target: '_blank', + rel: 'noopener noreferrer', + class: "twenty_project_activity_level_#{project.best_analysis.activity_level}", + title: project_activity_text(project, true) } +   + - if language + .lang_bg{ style: "background-color: ##{language_color(language.name)};" } + = link_to language.nice_name, language_path(language), class: 'language_name', + style: "color: ##{language_text_color(language.name)}" + - if project.rating_average.to_i > 0 + %span.org_project_rating + != rating_stars('rating_star', project.rating_average, mini: true) + .use-this-count + .count-value= project.user_count + .count-label= t('organizations.projects.i_use_this') + + .card-stats + .stat-box + .stat-title= t('organizations.projects.contributors') + .stat-rows + .stat-row + %span.stat-label= t('organizations.projects.affiliated') + = render partial: 'project_commit_status', locals: { diff: affiliated_committers_diff, + commit_count: twelve_month_summary.affiliated_committers_count, + prev_commit_count: previous_twelve_month_summary.affiliated_committers_count } + .stat-row + %span.stat-label= t('organizations.projects.outside') + = render partial: 'project_commit_status', locals: { diff: outside_committers_diff, + commit_count: twelve_month_summary.outside_committers_count, + prev_commit_count: previous_twelve_month_summary.outside_committers_count } + + .stat-box + .stat-title= t('organizations.projects.commits') + .stat-rows + .stat-row + %span.stat-label= t('organizations.projects.affiliated') + = render partial: 'project_commit_status', locals: { diff: affiliated_commits_diff, + commit_count: twelve_month_summary.affiliated_commits_count, + prev_commit_count: previous_twelve_month_summary.affiliated_commits_count } + .stat-row + %span.stat-label= t('organizations.projects.outside') + = render partial: 'project_commit_status', locals: { diff: outside_commits_diff, + commit_count: twelve_month_summary.outside_commits_count, + prev_commit_count: previous_twelve_month_summary.outside_commits_count } + + -# Desktop table view + %table.table.table-striped.table-condensed.portfolio-desktop-view %thead - %tr{ id: 'dingus-row' } - %td{ colspan: '10' }   %tr %th{ rowspan: 2, style: 'width: 190px;' }= t('organizations.projects.name') - %th.center{ rowspan: 2, style: 'width: 50px;' }   + %th.center{ rowspan: 2, style: 'width: 50px;', scope: 'col', aria: { label: t('organizations.projects.activity') } } %th.center{ rowspan: 2, style: 'width: 120px;' }= t('organizations.projects.primary_language') %th.center{ rowspan: 2, style: 'width: 90px;' }= t('organizations.projects.i_use_this') %th.center{ rowspan: 2, style: 'width: 110px;' }= t('organizations.projects.community_rating') - %th.center{ rowspan: 2, style: 'width: 15px;' } + %th{ rowspan: 2, style: 'width: 15px;', aria: { hidden: 'true' } } %th.center{ colspan: 2, style: 'width:250px' } %strong= t('organizations.projects.twelve_month_activity') %tr @@ -39,7 +104,7 @@ - project_activity_level_class(project, :twenty) %td.center{ rowspan: 3 } - if language.nil? - N/A + = t('organizations.projects.not_available') - else .lang_bg{ style: "background-color: ##{language_color(language.name)};" } = link_to language.nice_name, language_path(language), class: 'language_name', @@ -48,7 +113,7 @@ = project.user_count %td.center{ rowspan: 3 } - if project.rating_average.to_i.zero? - none + = t('organizations.projects.none') - else %span.org_project_rating != rating_stars('rating_star', project.rating_average, mini: true) diff --git a/app/views/organizations/show/_quick_reference.html.haml b/app/views/organizations/show/_quick_reference.html.haml index e8c06ba61..a22106797 100644 --- a/app/views/organizations/show/_quick_reference.html.haml +++ b/app/views/organizations/show/_quick_reference.html.haml @@ -1,4 +1,4 @@ -.well.org-summary-quick-ref +.oh-card.org-summary-quick-ref %h4= t('.title') %dl.dl-horizontal %dt.margin_bottom_10= t('.org_type') diff --git a/app/views/organizations/show/pictogram/_outside_projects.html.haml b/app/views/organizations/show/pictogram/_outside_projects.html.haml index b87408c16..dab22ac5c 100644 --- a/app/views/organizations/show/pictogram/_outside_projects.html.haml +++ b/app/views/organizations/show/pictogram/_outside_projects.html.haml @@ -4,4 +4,4 @@ %a.select_sub_view.append_history.block_ui.outside_projects{ href: '?view=outside_projects', url: '?view=outside_projects', update: '.org-data-table', current_class: 'outside_projects' } = t('organizations.outside_projects.outside_projects') -= image_tag("org_infographics/#{outside_projects_image}", class: 'center margin_top_10', width: '60px') += image_tag("org_infographics/#{outside_projects_image}", class: 'center margin_top_10', width: '60px', alt: t('organizations.outside_projects.outside_projects')) diff --git a/app/views/organizations/show/pictogram/_portfolio_projects.html.haml b/app/views/organizations/show/pictogram/_portfolio_projects.html.haml index 2aad7ff07..2b19338a1 100644 --- a/app/views/organizations/show/pictogram/_portfolio_projects.html.haml +++ b/app/views/organizations/show/pictogram/_portfolio_projects.html.haml @@ -6,6 +6,6 @@ %a.select_sub_view.append_history.block_ui.portfolio_projects{ href: '?view=portfolio_projects', url: '?view=portfolio_projects', update: '.org-data-table', current_class: 'portfolio_projects' } = projects_count - .portfolio_label.strong.portfolio_projects + .portfolio_label.strong = t('organizations.projects.title') -= image_tag("org_infographics/#{portfolio_projects_image}", class: 'center margin_top_10', width: '75px') += image_tag("org_infographics/#{portfolio_projects_image}", class: 'center margin_top_10', width: '75px', alt: t('organizations.projects.title')) diff --git a/app/views/passwords/create.html.haml b/app/views/passwords/create.html.haml new file mode 100644 index 000000000..12e4d77bd --- /dev/null +++ b/app/views/passwords/create.html.haml @@ -0,0 +1,34 @@ +- content_for(:html_title) { t('passwords.title') } + +.signup-page + / ── Left Panel ────────────────────────────────────────── + = render 'layouts/partials/auth_left_panel', + hero_lines: [t('passwords.new.hero_title')], + hero_accent: t('passwords.new.hero_accent'), + tagline: t('passwords.new.tagline') + + / ── Right Panel — success state ───────────────────────── + .signup-right-panel + .signup-form-card + .pwd-reset-success + .pwd-reset-success__icon + %i.fa.fa-check + + %h2.signup-form-title Request Received + + %p.pwd-reset-success__desc= t('.success') + + .pwd-reset-success__info + %p + If you don't see the email, check your spam folder or try resending the link. + + = link_to 'Try another email address', new_password_path, class: 'pwd-reset-retry-link' + + .pwd-reset-back + = link_to new_session_path, class: 'signin-helper-link signin-helper-link--primary' do + %i.fa.fa-arrow-left + Back to Sign In + + .signup-terms-note + Need help? + = link_to 'Contact Support', 'mailto:info@openhub.net', class: 'signup-terms-link' diff --git a/app/views/passwords/new.html.haml b/app/views/passwords/new.html.haml new file mode 100644 index 000000000..45337e021 --- /dev/null +++ b/app/views/passwords/new.html.haml @@ -0,0 +1,38 @@ +- content_for(:html_title) { t('passwords.title') } +- page_context[:suppress_flash] = true + +.signup-page + / ── Left Panel ────────────────────────────────────────── + = render 'layouts/partials/auth_left_panel', + hero_lines: [t('passwords.new.hero_title')], + hero_accent: t('passwords.new.hero_accent'), + tagline: t('passwords.new.tagline') + + / ── Right Panel ───────────────────────────────────────── + .signup-right-panel + = render partial: 'layouts/partials/alert' + .signup-form-card + .signup-form-header + %h2.signup-form-title= t('passwords.new.form_title') + %p.signup-form-subtitle= t('passwords.new.form_subtitle') + + = form_for :password, url: passwords_path do |form| + .signup-field-group + = form.label :email, class: 'signup-field-label', for: 'password_email' do + = t('passwords.new.email_label') + %span.pwd-required * + .signup-field-input-wrapper + %i.fa.fa-envelope-o.signup-field-icon + = form.email_field :email, class: 'signup-field-input', placeholder: t('passwords.new.email_placeholder') + + .signup-field-actions + = form.submit t('passwords.new.submit'), class: 'signup-submit-btn' + + .pwd-reset-back + = link_to new_session_path, class: 'signin-helper-link signin-helper-link--primary' do + %i.fa.fa-arrow-left + = t('passwords.new.back_to_sign_in') + + .signup-terms-note + = t('passwords.new.need_help') + = link_to t('passwords.new.contact_support'), 'mailto:info@openhub.net', class: 'signup-terms-link' diff --git a/app/views/people/_claimed_person.html.haml b/app/views/people/_claimed_person.html.haml index c3d6aed8c..6c7f9c2df 100644 --- a/app/views/people/_claimed_person.html.haml +++ b/app/views/people/_claimed_person.html.haml @@ -10,70 +10,64 @@ res end sorted_cbl = cbl.sort_by { |_, v| v[:commits] }.reverse -.well.no_padding - .pull-left.avatar_new - = avatar_for account, size: 128 - .pull-left.account_details - %h3 - %a.margin_top_20{ href: account_path(account), title: account.name }= truncate(account.name, length: 18) - - markup_raw = account.markup ? account.markup.raw : '' - %p.small.word_break_all= truncate(Markup.create(raw: markup_raw).first_line, length: 75) - .small.member_info - - if account.projects.any? - %p - - what = pluralize(account.projects.count, t('.project')) - %a{ href: account_projects_path(account) }= t('.manages', what: what) - %p= t('.open_hub_member_since', when: account.created_at.strftime('%B %Y')) - -if only_device? - .pull-left.kudo_rank - = claimed_person.kudo_rank || '1' - .mini-badges-section.pull-left.kudo_badge - = render 'accounts/badges_row', badges: [BadgeDecorator.new(Badge::KudoRankBadge.new(account))] - .pull-left.commits_summary - - if vf && vf.commits > 0 && @positions_map.any? - .commits_count.pull-left - = vf.commits - %br - %span - %a{ href: account_positions_path(account), style: 'font-size: 12px;' } - = pluralize_without_count(vf.commits, t('.commit')) - .pull-left.in= t('.in_dots') - - if sorted_cbl.empty? - .pull-left.language_summary.no_lang_summary!= t('.no_detected_languages') - - else - .pull-left.language_summary - .pull-left{ style: 'min-height: 99px;' } - - sorted_cbl.first(3).each do |lang_name, lang_hash| - - style = "background-color:##{language_color(lang_name)}; color: ##{language_text_color(lang_name)};" - .language_name{ style: style } - %span= link_to lang_hash[:nice_name], language_path(lang_name) - .pull-left.other_language +.people-card + .people-card__body + .people-card__row + .people-card__avatar-info + = avatar_for account, size: 96 + .people-card__info + %h3.people-card__name + = link_to truncate(account.name, length: 18), account_path(account), title: account.name + - if account.projects.any? + %p.people-card__meta + - what = pluralize(account.projects.count, t('.project')) + = link_to t('.manages', what: what), account_projects_path(account) + %p.people-card__meta= t('.open_hub_member_since', when: account.created_at.strftime('%B %Y')) + .people-card__commits + .people-card__commits-count= vf && vf.commits > 0 ? vf.commits : 0 + .people-card__commits-label + - if vf && vf.commits > 0 + = link_to pluralize_without_count(vf.commits, t('.commit')), account_positions_path(account) + - else + = t('.commit').pluralize + .people-card__activity + - if vf && vf.commits > 0 && @positions_map.any? + .people-card__row-langs + %span.people-card__label= t('.in_dots') + .people-card__lang-tags + - if sorted_cbl.empty? + %span.people-card__no-lang= t('.no_detected_languages') + - else + - sorted_cbl.first(3).each_with_index do |(lang_name, lang_hash), i| + - if i == 0 + %span.people-card__lang-primary= link_to lang_hash[:nice_name], language_path(lang_name) + - else + %span.people-card__lang-secondary= link_to lang_hash[:nice_name], language_path(lang_name) + .people-card__row-projects + %span.people-card__label to... + .people-card__project-list + - pos_ids.each do |pos_id| + - project = @positions_map[pos_id].project + - next unless project + .people-card__project-item + %a{ href: project_path(project), title: project.name } + - project_icon(project, :small, width: 20, class: 'people-card__project-icon') + = link_to truncate(project.name, length: 8), project_path(project), class: 'people-card__project-name' + %p.people-card__other-links - if sorted_cbl.length > 3 - pluaral_term = pluralize(sorted_cbl.length - 3, t('.other_language')) - %a{ href: account_languages_path(account) }= t('.and_thing', thing: pluaral_term) + = link_to t('.and_thing', thing: pluaral_term), account_languages_path(account), class: 'people-card__link' - else - %a{ href: account_languages_path(account) }= t('.see_commits_by_languages') - - .pull-left.in to ... - .pull-left.project_summary - - pos_ids.each do |pos_id| - - project = @positions_map[pos_id].project - - next unless project - .margin_bottom_10 - %a{ href: project_path(project), title: project.name } - - project_icon(project, :small, width: 24, class: 'logo') - %a.margin_left_5{ href: project_path(project) }= truncate(project.name, length: 8) - .pull-left.other_language.other_projects - - if more_pos_count > 0 - - pluaral_term = pluralize(more_pos_count, t('.other_project')) - %a.more_commits_padding{ href: account_positions_path(account) }= t('.and_thing', thing: pluaral_term) + = link_to t('.see_commits_by_languages'), account_languages_path(account), class: 'people-card__link' + %span.people-card__link-dot • + - if more_pos_count > 0 + - pluaral_term = pluralize(more_pos_count, t('.other_project')) + = link_to t('.and_thing', thing: pluaral_term), account_positions_path(account), class: 'people-card__link' + - else + = link_to t('.see_commits_by_project'), account_positions_path(account), class: 'people-card__link' - else - %a.more_commits_padding{ href: account_positions_path(account) }= t('.see_commits_by_project') - - else - %p.no_commits No commit data to display - - unless only_device? - .pull-left.kudo_rank - = claimed_person.kudo_rank || '1' - .mini-badges-section.pull-left.kudo_badge - = render 'accounts/badges_row', badges: [BadgeDecorator.new(Badge::KudoRankBadge.new(account))] - .clear_both + %p.people-card__no-commits No commit data to display + .people-card__kudos + .people-card__kudos-rank= claimed_person.kudo_rank || '1' + .people-card__kudos-badge + = render 'accounts/badges_row', badges: [BadgeDecorator.new(Badge::KudoRankBadge.new(account))] diff --git a/app/views/people/_people.html.haml b/app/views/people/_people.html.haml index 7a64bdde6..028e6af20 100644 --- a/app/views/people/_people.html.haml +++ b/app/views/people/_people.html.haml @@ -1,14 +1,13 @@ - if @claimed_people.any? - %h3.margin_bottom_15= t('.account_holders') + %h2.people-section-title= t('.account_holders') = render partial: 'claimed_person', collection: @claimed_people - - if @claimed_people.total_entries > 3 - %p - %a.btn.btn-mini.btn-primary#more_account{ href: accounts_path(query: params[:query]) }= t('.more_account_holders') + - if @claimed_people.total_pages > 3 + = link_to t('.more_account_holders'), accounts_path(query: params[:query]), class: 'people-more-btn mb-5', id: 'more_account' - if @unclaimed_people.any? - %h3.margin_bottom_15= t('.unclaimed_committer_ids') - - @unclaimed_people.each do |_, people| - = render partial: 'unclaimed_person', locals: { people: people } - - if @unclaimed_people_count > 3 - %p - %a.btn.btn-mini.btn-primary#more_unclaimed{ href: committers_path(query: params[:query]) }= t('.more_commiters') + .unclaimed-section.mt-5 + %h2.people-section-title= t('.unclaimed_committer_ids') + - @unclaimed_people.each do |_, people| + = render partial: 'unclaimed_person', locals: { people: people } + - if @unclaimed_people_count.to_i > 3 + = link_to t('.more_commiters'), committers_path(query: params[:query]), class: 'people-more-btn', id: 'more_unclaimed' diff --git a/app/views/people/_unclaimed_person.html.haml b/app/views/people/_unclaimed_person.html.haml index c2eed4ba9..5207ca50a 100644 --- a/app/views/people/_unclaimed_person.html.haml +++ b/app/views/people/_unclaimed_person.html.haml @@ -6,25 +6,21 @@ unclaimed_people ||= people.to_a.first(UNCLAIMED_TILE_LIMIT) = form_tag(claim_committer_path(name_id), method: :post) do - .well.col-md-12.col-sm-12.col-xs-12.unclaimed_committers_box - .header_row{ style: 'padding-bottom: 0;' } - .pull-left - %h4.no_margin_bottom{ style: 'margin: 0;' } - = from_show_page ? name : link_to(h(truncate(name, length: 70)), committer_path(name_id)) - .pull-right - = submit_tag t('.claim_these_contributions'), class: 'btn btn-mini btn-primary' - .clearfix - - .entire_commits_container - - unclaimed_people.each do |unclaimed_person| - - project = unclaimed_person.project - .inner - .single_container - .group - = render 'committers/unclaimed_tile', project: project, name_fact: unclaimed_person.name_fact - %input.hidden{ type: 'checkbox', name: 'project_ids[]', value: project.id, checked: 'checked' } - - if people.length > UNCLAIMED_TILE_LIMIT && !from_show_page - .inner.more - .single_container.show_more - %span - = link_to t('.and_more', length: people.length - UNCLAIMED_TILE_LIMIT), committer_path(name_id) + .people-card.unclaimed-card + .people-card__body + .unclaimed-card__header + %h4.unclaimed-card__name + - if from_show_page + = name + - else + = link_to(h(truncate(name, length: 70)), committer_path(name_id)) + = submit_tag t('.claim_these_contributions'), class: 'btn btn-primary unclaimed-card__btn' + .unclaimed-card__grid + - unclaimed_people.each do |unclaimed_person| + - project = unclaimed_person.project + .unclaimed-card__item + = render 'committers/unclaimed_tile', project: project, name_fact: unclaimed_person.name_fact + %input{ type: 'hidden', name: 'project_ids[]', value: project.id } + - if people.length > UNCLAIMED_TILE_LIMIT && !from_show_page + .unclaimed-card__more + = link_to t('.and_more', length: people.length - UNCLAIMED_TILE_LIMIT), committer_path(name_id) diff --git a/app/views/people/index.html.haml b/app/views/people/index.html.haml index 66d359279..f9e40b71d 100644 --- a/app/views/people/index.html.haml +++ b/app/views/people/index.html.haml @@ -2,9 +2,12 @@ - page_context[:select_top_menu_nav] = :select_people %meta{ name: 'ROBOTS', content: 'NOINDEX, NOFOLLOW' } -%h1.margin_bottom_15= t('.people') +.people-page-container + .people-page-header + %h1.people-page-title= t('.people') + = render 'shared/global_search', placeholder: t('shared.global_search.placeholder') -= render_people_list + = render_people_list -- if params[:query].present? && @claimed_people.empty? && @unclaimed_people.empty? - = render partial: 'shared/alert', locals: { message: t('shared.search_dingus.no_match_found.no_match') } + - if params[:query].present? && @claimed_people.empty? && @unclaimed_people.empty? + = render partial: 'shared/alert', locals: { message: t('shared.search_dingus.no_match_found.no_match') } diff --git a/app/views/people/rankings.html.haml b/app/views/people/rankings.html.haml index fbdf6fb33..20b4e8772 100644 --- a/app/views/people/rankings.html.haml +++ b/app/views/people/rankings.html.haml @@ -24,4 +24,4 @@ %dl.dl-horizontal %dt= t('.kudo_position') %dd!= people_position(person) -= will_paginate @people += render 'shared/modern_pagination', collection: @people diff --git a/app/views/permissions/show.html.haml b/app/views/permissions/show.html.haml index f9239221f..b2b32006d 100644 --- a/app/views/permissions/show.html.haml +++ b/app/views/permissions/show.html.haml @@ -2,15 +2,17 @@ - page_context[:select_footer_nav] = nil = javascript_include_tag 'permissions.js' -.project_content_title - %h2.float_left - = link_to t(:settings), [:settings, @parent] - : #{t('permissions.permissions')} - = project_analysis_timestamp(@project) if @project += render partial: 'layouts/partials/alert' .row - .col-sm-6.col-sm-offset-2 - .well + .col-md-6.col-md-offset-1 + %h2.float_left + = link_to t(:settings), [:settings, @parent] + : #{t('permissions.permissions')} + +.row + .col-md-6.col-md-offset-1 + .oh-card - url = polymorphic_path([:permissions, @parent]) = form_for @permission, as: :permission, url: url, html: { method: :put, id: 'permission_show' } do |f| %fieldset @@ -33,16 +35,23 @@ .actions - if current_user_can_manage? - = submit_tag t(:save_changes), id: 'submit', class: 'btn-sm btn-primary', style: 'display: none' + = button_tag t(:save_changes), type: 'submit', id: 'submit', class: 'btn btn-sm btn-primary', style: 'display: none' - else - = disabled_button t(:save_changes), class: 'btn-primary' - -%h4.nomargin - %span.soft= t 'permissions.show.about_permissions_1' - = t 'permissions.show.about_permissions_2' -.col-sm-6 - %ul - - parent_name = @parent.class.name - %li= t 'permissions.show.help_1', name: parent_name - %li= t 'permissions.show.help_2', name: parent_name.downcase - %li= t 'permissions.show.help_3', upper_name: parent_name, name: parent_name.downcase + = disabled_button t(:save_changes), class: 'btn-sm btn-primary' +.row + .col-md-6.col-md-offset-1 + .about-account-basics-card + .card-header + %h4.card-title + %span.soft= t 'permissions.show.about_permissions_1' + = t 'permissions.show.about_permissions_2' + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + .card-content + .about-content-wrapper + .col-md-10 + - parent_name = @parent.class.name + %ul + %li= t 'permissions.show.help_1', name: parent_name + %li= t 'permissions.show.help_2', name: parent_name.downcase + %li= t 'permissions.show.help_3', upper_name: parent_name, name: parent_name.downcase diff --git a/app/views/positions/_commits_by_individual_project_highchart.html.haml b/app/views/positions/_commits_by_individual_project_highchart.html.haml index 45a92d0bf..d1e5b818b 100644 --- a/app/views/positions/_commits_by_individual_project_highchart.html.haml +++ b/app/views/positions/_commits_by_individual_project_highchart.html.haml @@ -2,5 +2,5 @@ - pname = position.project.name[0..20] - chart_data = ChartDecorator.new.project_commit_history(@account, position.project_id) .margin_bottom_5 - .chart-with-data{ id: "project_contributions_#{position.project_id}", 'data-value' => chart_data.as_json, - 'data-pname' => pname, style: 'width: 910px; height: 170px' } + .chart-with-data.watermark900white{ id: "project_contributions_#{position.project_id}", 'data-value' => chart_data.as_json, + 'data-pname' => pname, style: 'height: 170px' } diff --git a/app/views/positions/_commits_by_individual_projects.html.haml b/app/views/positions/_commits_by_individual_projects.html.haml index 280701a42..e3460af36 100755 --- a/app/views/positions/_commits_by_individual_projects.html.haml +++ b/app/views/positions/_commits_by_individual_projects.html.haml @@ -1,4 +1,4 @@ -%div{ class: position.decorate.analyzed_class_name } +%div.oh-card{ class: position.decorate.analyzed_class_name } %div .one-project-header-left %a.pull-left.logo{ href: project_path(position.project) } diff --git a/app/views/positions/_commits_by_projects_extended.html.haml b/app/views/positions/_commits_by_projects_extended.html.haml index f7c5490e6..87b211e22 100755 --- a/app/views/positions/_commits_by_projects_extended.html.haml +++ b/app/views/positions/_commits_by_projects_extended.html.haml @@ -1,3 +1,3 @@ .padding_one_top - chart_data = ChartDecorator.new.combined_commit_history(@account) -.chart-with-data#all_projects{ 'data-value' => chart_data.as_json, style: 'width: 940px; height: 300px' } +.chart-with-data.oh-card#all_projects{ 'data-value' => chart_data.as_json, style: 'height: 300px' } diff --git a/app/views/positions/_edit_link.html.haml b/app/views/positions/_edit_link.html.haml index 5322cec88..bc27fbe1b 100644 --- a/app/views/positions/_edit_link.html.haml +++ b/app/views/positions/_edit_link.html.haml @@ -1,5 +1,5 @@ - if my_account?(@account) || current_user_is_admin? - - edit_link_classes = 'btn btn-mini btn-info pull-right margin_top_5' + - edit_link_classes = 'btn btn-medium btn-primary pull-right margin_top_5' = link_to edit_account_position_path(@account, position), class: edit_link_classes do %i.icon-pencil = t('edit') diff --git a/app/views/positions/_fields.html.haml b/app/views/positions/_fields.html.haml index 347611a54..b60c1aaee 100644 --- a/app/views/positions/_fields.html.haml +++ b/app/views/positions/_fields.html.haml @@ -1,114 +1,105 @@ - project_name = params[:position].present? ? params[:position][:project_oss] : position.try(:project).try(:name) - project_name ||= params[:project_name] -%fieldset.row-fluid - .form-group - %label.control-label.required= t('.project_name') - .controls - = f.text_field :project_oss, class: 'col-md-4 autocompletable', - value: project_name, - data: { source: '/autocompletes/project' } - - error_tag(position, :project_oss) - %p.help-block= t('.enter_openhub_project_name') +%fieldset.position-fields + .position-field-group + %label.position-label.required{ for: 'position_project_oss' }= t('.project_name') + = f.text_field :project_oss, class: 'form-control autocompletable', + value: project_name, + data: { source: '/autocompletes/project' } + - error_tag(position, :project_oss) + %p.position-help= t('.enter_openhub_project_name') - .form-group - %label.control-label= t('.committer_name') - .controls - = f.text_field :committer_name, class: 'col-md-4 autocompletable', - value: params[:committer_name], - data: { source: '/autocompletes/contributions', prerequisite_input: true } - - error_tag(position, :committer_name) - %p.help-block - = t('.enter_scm_committer_id') - %br - = t('.leave_this_blank') + .position-field-group + %label.position-label{ for: 'position_committer_name' }= t('.committer_name') + = f.text_field :committer_name, class: 'form-control autocompletable', + value: params[:committer_name], + data: { source: '/autocompletes/contributions', prerequisite_input: true } + - error_tag(position, :committer_name) + %p.position-help + = t('.enter_scm_committer_id') + %br + = t('.leave_this_blank') - .form-group.position_affiliation - %label.control-label= t('affiliation') + .position-field-group.position_affiliation + %label.position-label{ for: 'position_organization_id' }= t('affiliation') .controls.chosen#value_select - = f.select :organization_id, OrganizationDecorator.select_options, {}, class: 'col-md-4 chzn-select value-select' + = f.select :organization_id, OrganizationDecorator.select_options, {}, class: 'form-control chzn-select value-select' - error_tag(position, :organization_id) - = f.text_field :organization_name, class: 'col-md-4 hidden', placeholder: t('.affiliation.placeholder') + = f.text_field :organization_name, class: 'form-control hidden', placeholder: t('.affiliation.placeholder') = f.hidden_field :affiliation_type - error_tag(position, :organization_name) - %p.help-block= t('.enter_name_of_company_or_organization') + %p.position-help= t('.enter_name_of_company_or_organization') - .form-group - %label.control-label= t('.role_on_project') - .controls - = f.text_field :title, class: 'col-md-4' + .position-field-group + %label.position-label{ for: 'position_title' }= t('.role_on_project') + = f.text_field :title, class: 'form-control' - .clearfix + .position-field-group + %label.position-label{ for: 'position_description' }= t('.describe_contributions') + = f.text_area :description, rows: 8, class: 'form-control position-textarea', value: @position.description - .form-group - %label.control-label= t('.describe_contributions') - .controls - = f.text_area :description, rows: 10, class: 'col-md-4', value: @position.description + - date_selector_options = { order: [:month, :year], prompt: true, end_year: Time.current.year, start_year: 1980 } - .clearfix + .position-field-group + %fieldset.position-fieldset + %legend.position-label= t('.when_did_u_start') + .position-radio-group + %label.position-radio-label + = radio_button_tag :start_date_type, nil, !position.start_date?, class: 'choose-automatic' + = t('.calculate_from_first_commit') + %label.position-radio-label + = radio_button_tag :start_date_type, nil, position.start_date?, id: 'manual-start-date' + = t('.specify_date_manually') + .position-date-group{ role: 'group', 'aria-label' => t('.when_did_u_start') } + = f.date_select :start_date, date_selector_options, { class: 'chzn-select value-select' } + - error_tag(position, :start_date) - - date_selector_options = { order: [:month, :year], prompt: true, end_year: Time.current.year, start_year: 1980 } - .form-group - %label.control-label= t('.when_did_u_start') - .controls - = radio_button_tag :start_date_type, nil, !position.start_date?, class: 'choose-automatic' - = t('.calculate_from_first_commit') -    - = radio_button_tag :start_date_type, nil, position.start_date?, id: 'manual-start-date' - = t('.specify_date_manually') - %p.chosen#value_select - %br - = f.date_select :start_date, date_selector_options, class: 'chzn-select value-select' - .row - .col-md-5 - - error_tag(position, :start_date) - .form-group - %label.control-label= t('.when_did_u_stop') - .controls - = f.radio_button :ongoing, false, checked: @position.new_record?, class: 'choose-automatic' - = t('.calculate_from_last_commit') -    - = f.radio_button :ongoing, true - = t('.i_have_this_position') -    - = f.radio_button :ongoing, false, id: 'manual-stop-date' - = t('.specify_date_manually') -    - %p.chosen#value_select - %br - = f.date_select :stop_date, date_selector_options, class: 'chzn-select value-select' - .row - .col-md-5 - - error_tag(position, :stop_date) - %p - %i.icon-chevron-down -   - = link_to t('.describe_projects_and_languages'), 'javascript:', class: :collapsed - = link_to t('.describe_projects_and_languages'), 'javascript:', class: 'expanded hidden' - %p + .position-field-group + %fieldset.position-fieldset + %legend.position-label= t('.when_did_u_stop') + .position-radio-group + %label.position-radio-label + = f.radio_button :ongoing, false, checked: @position.new_record?, class: 'choose-automatic' + = t('.calculate_from_last_commit') + %label.position-radio-label + = f.radio_button :ongoing, true + = t('.i_have_this_position') + %label.position-radio-label + = f.radio_button :ongoing, false, id: 'manual-stop-date' + = t('.specify_date_manually') + .position-date-group{ role: 'group', 'aria-label' => t('.when_did_u_stop') } + = f.date_select :stop_date, date_selector_options, { class: 'chzn-select value-select' } + - error_tag(position, :stop_date) + + .position-field-group + .position-expand-toggle + = link_to 'javascript:', class: 'collapsed' do + %i.fa.fa-chevron-down.position-expand-icon + = t('.describe_projects_and_languages') + = link_to 'javascript:', class: 'expanded hidden' do + %i.fa.fa-chevron-up.position-expand-icon + = t('.describe_projects_and_languages') .hidden#additional-fields - .form-group - %label.control-label= t('.what_did_u_program_in') - .controls - #position_languages{ style: 'line-height: 25px;' } - = render 'language_selector' - %p.help-block= t('.list_regular_languages') - .form-group - %label.control-label= t('.describe_open_source_projects') - .controls#project-experience-form + .position-field-group + %label.position-label{ for: 'position_language_exp' }= t('.what_did_u_program_in') + #position_languages + = render 'language_selector' + %p.position-help= t('.list_regular_languages') + .position-field-group + %label.position-label= t('.describe_open_source_projects') + #project-experience-form - @position.project_experiences.build = f.fields_for :project_experiences do |ff| - .clearfix - %p.help-block= t('.enter_project_name') - = ff.text_field :project_name, class: 'col-md-4 autocompletable' - %button.margin_left_5.btn.btn-small.remove{ type: :button } - = t('remove') + .position-project-exp + %p.position-help= t('.enter_project_name') + = ff.text_field :project_name, class: 'form-control autocompletable', aria: { label: t('.enter_project_name') } + %button.btn.btn-sm.btn-default.remove{ type: :button }= t('remove') - error_tag(ff.object, :project_name) + = button_tag 'Add another project', class: 'btn btn-sm btn-default', type: :button, id: 'add-project-experience' - = button_tag 'Add another project', class: 'btn btn-default', type: :button, id: 'add-project-experience' - - .actions + .position-actions - submit_text = params[:invite].blank? ? :Submit : :Next - = submit_tag(submit_text, class: 'btn btn-sm btn-primary', id: :saveForm) + = submit_tag(submit_text, class: 'btn btn-primary position-btn-submit', id: :saveForm) - if allow_delete = link_to 'Delete Position', [@account, @position], method: :delete, data: { confirm: t('.delete_forever') }, - class: 'btn btn-sm btn-danger' + class: 'btn btn-danger position-btn-delete' diff --git a/app/views/positions/_language_selector.html.haml b/app/views/positions/_language_selector.html.haml index 0d89fbab3..75926d49d 100644 --- a/app/views/positions/_language_selector.html.haml +++ b/app/views/positions/_language_selector.html.haml @@ -1,5 +1,5 @@ .chosen.select-language-experiences#value_select - %select.chzn-select{ name: 'position[language_exp][]', multiple: true, 'data-placeholder' => t('.select_prompt') } + %select.chzn-select{ name: 'position[language_exp][]', id: 'position_language_exp', multiple: true, 'data-placeholder' => t('.select_prompt') } - all_languages = Language.order(Arel.sql('lower(nice_name) ASC')).map { |l| [l.id, l.nice_name] } - existing_experience_ids = @position.language_experiences.pluck(:language_id).presence || params[:position].try{|position| position[:language_exp] }.to_a.map(&:to_i) - all_languages.each do |id, language_name| diff --git a/app/views/positions/_position.html.haml b/app/views/positions/_position.html.haml index 9b64a5c80..250beffe2 100644 --- a/app/views/positions/_position.html.haml +++ b/app/views/positions/_position.html.haml @@ -1,6 +1,6 @@ .row .col-md-7 - .well + .oh-card - unless params_id_is_total? - if current_user == position.account || current_user_is_admin? %span.pull-right diff --git a/app/views/positions/edit.html.haml b/app/views/positions/edit.html.haml index 274718c44..c69a4644a 100644 --- a/app/views/positions/edit.html.haml +++ b/app/views/positions/edit.html.haml @@ -3,11 +3,13 @@ - content_for :html_title do = t('.edit_title', name: @account.name) -%h3 - = link_to t('contribution'), account_positions_path(@account) - = precede ' : ' do - = t('edit') - -.well.col-md-11.center-block - = form_for [@account, @position], html: { id: :position_form } do |f| - = render 'fields', f: f, position: @position, allow_delete: true +#position-edit-page + .row.position-edit-row + .col-md-8.col-sm-10.col-xs-12.center-block + %h1.position-edit-title + = link_to t('contribution'), account_positions_path(@account) + %span.position-edit-title__sep › + = t('edit') + .oh-card + = form_for [@account, @position], html: { id: :position_form } do |f| + = render 'fields', f: f, position: @position, allow_delete: true diff --git a/app/views/positions/index.html.haml b/app/views/positions/index.html.haml index cb1e3399d..af68d7122 100755 --- a/app/views/positions/index.html.haml +++ b/app/views/positions/index.html.haml @@ -5,23 +5,19 @@ page_context[:select_top_menu_nav] = :select_people page_context[:select_footer_nav] = :positions -%h2.pull-left= t('contribution') -.padding_top_5 - = render 'accounts/show/account_analysis_timestamp', best_account_analysis: @account.best_account_analysis - -.clearfix #positions-index-page + .positions-section-header + %h2= t('contribution') - if @account.positions.exists? = render 'commits_by_projects_extended' .mezzo.padding_one_top - @positions.each do |position| = render 'commits_by_individual_projects', position: position - = will_paginate @positions + = render 'shared/modern_pagination', collection: @positions - else %p= t('.no_contributions_to_display') - -- if current_user == @account - = link_to new_account_position_path(@account), class: 'btn btn-small btn-primary' do - %i.icon-plus-sign -   - = t('.claim_contribution') + - if current_user == @account + = link_to new_account_position_path(@account), class: 'btn btn-medium btn-primary' do + %i.icon-plus-sign +   + = t('.claim_contribution') diff --git a/app/views/positions/new.html.haml b/app/views/positions/new.html.haml index 2d699d7a2..bbb89f277 100644 --- a/app/views/positions/new.html.haml +++ b/app/views/positions/new.html.haml @@ -2,16 +2,16 @@ = t('.position_new_title', name: @account.name) - page_context[:select_footer_nav] = :positions -#positions_new - .margin_left_15 - %h3 - = link_to t('.contribution'), account_positions_path(@account) - = precede ' : ' do +#position-edit-page + .row.position-edit-row + .col-md-8.col-sm-10.col-xs-12.center-block + %h1.position-edit-title + = link_to t('.contribution'), account_positions_path(@account) + %span.position-edit-title__sep › = t('new') - - .well.center-block.margin_left_15 - - if params[:invite].present? - %p= t('.provide_extra_information') - = form_for [@account, @position], html: { id: :position_form } do |f| - = hidden_field_tag :invite, params[:invite] - = render 'fields', f: f, position: @position, allow_delete: false + .oh-card + - if params[:invite].present? + %p.position-help= t('.provide_extra_information') + = form_for [@account, @position], html: { id: :position_form } do |f| + = hidden_field_tag :invite, params[:invite] + = render 'fields', f: f, position: @position, allow_delete: false diff --git a/app/views/posts/_post.html.haml b/app/views/posts/_post.html.haml index edcdc2d0c..2be2fcb0c 100644 --- a/app/views/posts/_post.html.haml +++ b/app/views/posts/_post.html.haml @@ -1,9 +1,9 @@ -.well.posts{ style: post.account.access.admin? ? 'background: #D5E3E9;' : 'background: #F6F6F6' } +.oh-card.posts{ style: post.account.access.admin? ? 'background: #D5E3E9;' : 'background: #F6F6F6' } = find_and_preserve(markdown_format(post.body)) .clearfix.margin_top_10 .col-md-3.pull-right .pull-left - %a= avatar_img_for post.account, 42 + %a{ href: account_path(post.account), 'aria-label' => "View #{h(post.account.name)}'s profile" }= avatar_img_for post.account, 42 .pull-left.margin_left_5 %a.avatar_name{ href: account_path(post.account) } = truncate(h(post.account.name), length: 18) diff --git a/app/views/posts/_posts.html.haml b/app/views/posts/_posts.html.haml index 6b38b7e70..95af25874 100644 --- a/app/views/posts/_posts.html.haml +++ b/app/views/posts/_posts.html.haml @@ -1,4 +1,4 @@ -.well.posts +.oh-card.posts .col-md-8{ style: 'width: 540px' } %h4.margin_top_0#post_body_link_size = link_to h(posts.topic.forum.name), forum_path(posts.topic.forum) diff --git a/app/views/posts/edit.html.haml b/app/views/posts/edit.html.haml index 476a489da..3b66bfaff 100644 --- a/app/views/posts/edit.html.haml +++ b/app/views/posts/edit.html.haml @@ -19,7 +19,7 @@ = button_to t('.delete'), topic_post_path(@topic, @post), method: :delete, data: { confirm: t('.delete_confirmation') }, class: 'btn btn-danger btn-mini' -.well.col-md-12 +.oh-card.col-md-12 .col-md-9 = form_for([@topic, @post], url: topic_post_path(@topic, @post), html: { method: 'put' }) do |f| = render partial: 'fields', locals: { f: f } diff --git a/app/views/posts/index.html.haml b/app/views/posts/index.html.haml index b178612b7..7c41e2708 100644 --- a/app/views/posts/index.html.haml +++ b/app/views/posts/index.html.haml @@ -11,7 +11,7 @@ = render partial: '/forums/notice' = render partial: 'shared/search_dingus', locals: { collection: @posts, sort_context: :posts } = render partial: 'posts/posts', collection: @posts - = will_paginate @posts + = render 'shared/modern_pagination', collection: @posts - else %h1 = link_to t('.forums'), forums_path @@ -20,4 +20,4 @@ = render partial: '/forums/notice' = render partial: 'shared/search_dingus', locals: { collection: @posts, sort_context: :posts } = render partial: 'posts/posts', collection: @posts - = will_paginate @posts + = render 'shared/modern_pagination', collection: @posts diff --git a/app/views/privacy/_authorized_apps.html.haml b/app/views/privacy/_authorized_apps.html.haml index ea3be1513..2af6ddcd0 100644 --- a/app/views/privacy/_authorized_apps.html.haml +++ b/app/views/privacy/_authorized_apps.html.haml @@ -18,4 +18,4 @@ - path = account_revoke_oauth_access_path(@account, oauth_application) = link_to t('.remove'), path, class: 'btn btn-danger' - else - %span.well.col-md-10.margin_left_20= t('.no_registered_applications') + %span.oh-card.col-md-10.margin_left_20= t('.no_registered_applications') diff --git a/app/views/privacy/_privacy_text.html.haml b/app/views/privacy/_privacy_text.html.haml index fb55bb605..060232afd 100644 --- a/app/views/privacy/_privacy_text.html.haml +++ b/app/views/privacy/_privacy_text.html.haml @@ -1,13 +1,18 @@ -.row.padding_one_top -%h4.margin_left_15.margin_bottom_0 - .soft.inline= t('.about') - = t('.privacy') -.col-md-5 - %ul.margin_left_40 - %li= t('.privacy_policy_html', href: link_to(t('.privacy_policy'), 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Open-Hub-Privacy-Policy')) - %li= t('.admin_emails_from_openhub') - %li= t('.opting_in') -.col-md-5 - %ul.margin_left_40 - %li= t('.opting_out') - %li= t('.openhub_oauth_html', href: link_to(t('.oauth_api'), 'http://blog.openhub.net/oauth/')) +.about-account-basics-card.alter-password-about + .card-header + %h4.card-title + %span.soft= t('.about') + = t('.privacy') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + .card-content + .about-content-wrapper + .col-md-6 + %ul + %li!= t('.privacy_policy_html', href: link_to(t('.privacy_policy'), 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Open-Hub-Privacy-Policy', target: '_blank', rel: 'noopener noreferrer')) + %li= t('.admin_emails_from_openhub') + %li= t('.opting_in') + .col-md-6 + %ul + %li= t('.opting_out') + %li!= t('.openhub_oauth_html', href: link_to(t('.oauth_api'), 'https://github.com/blackducksoftware/ohloh_api/tree/main/examples/oauth2', target: '_blank', rel: 'noopener noreferrer')) diff --git a/app/views/privacy/edit.html.haml b/app/views/privacy/edit.html.haml index 31b4ae2ba..fe56bc9c3 100644 --- a/app/views/privacy/edit.html.haml +++ b/app/views/privacy/edit.html.haml @@ -4,42 +4,47 @@ - account_context -%h2= t('.settings_privacy_html', href: link_to(t('.settings'), settings_account_path(@account))) -.row - .col-md-6 - .well.well-lg - = form_for(@account, url: account_privacy_account_path) do |f| - %fieldset - %legend.margin_bottom_0= t('.manage_email') - .control-group.col-md-12.margin_left_5 - %h4= t('.administrative_email') - %p.admin_email_notice= t('.send_admin_email') - %br - %h4= t('.other_openhub_email') - %p.admin_email_notice= t('.admin_email_notice') - %br - .controls.admin_email_notice - %p.email_opt_status - = f.radio_button(:email_master, 'false', id: 'account_email_master_false') - %span= t('.dont_send_email') - %br - = f.radio_button(:email_master, 'true', id: 'account_email_master_true') - %span= t('.do_send_email') - %ul.chosen#email_status{ style: 'padding-left: 25px' } - %li.margin_bottom_13 - %span= t('.please') - = f.select :email_kudos, [['include', true], ['exclude', false]], {}, class: 'select_box value-select' - %span= t('.notices_ive_received') - %br - %li{ style: 'width: 370px' } - %span= t('.please') - = f.select :email_posts, [['include', true], ['exclude', false]], {}, class: 'select_box value-select' - %span= t('.replies_to_forum_topics') +%h2.alter-password-heading + = link_to t('.settings'), settings_account_path(@account) + = ' : ' + %span.account-basics-title= t('privacy.privacy_text.privacy') +#privacy_edit_page + .row + .col-md-6 + .oh-card + = form_for(@account, url: account_privacy_account_path) do |f| + %fieldset + %legend.margin_bottom_0= t('.manage_email') + .control-group.col-md-12.margin_left_5 + %h4= t('.administrative_email') + %p.admin_email_notice= t('.send_admin_email') %br - %p.help-inline= t('.never_ask_for_personal_email') - .actions + %h4= t('.other_openhub_email') + %p.admin_email_notice= t('.admin_email_notice') %br - = submit_tag(t('.save_changes'), id: 'Submit', class: 'btn btn-sm btn-primary') - - = render partial: 'authorized_apps' - = render partial: 'privacy_text' + .controls.admin_email_notice + %p.email_opt_status + = f.radio_button(:email_master, 'false', id: 'account_email_master_false') + = f.label(:email_master, t('.dont_send_email'), value: 'false') + %br + = f.radio_button(:email_master, 'true', id: 'account_email_master_true') + = f.label(:email_master, t('.do_send_email'), value: 'true') + %ul.chosen#email_status{ style: 'padding-left: 25px' } + %li.margin_bottom_13 + %span= t('.please') + = f.label :email_kudos, t('.notices_ive_received'), class: 'sr-only' + = f.select :email_kudos, [['include', true], ['exclude', false]], {}, class: 'select_box value-select' + %span= t('.notices_ive_received') + %br + %li{ style: 'width: 370px' } + %span= t('.please') + = f.label :email_posts, t('.replies_to_forum_topics'), class: 'sr-only' + = f.select :email_posts, [['include', true], ['exclude', false]], {}, class: 'select_box value-select' + %span= t('.replies_to_forum_topics') + %br + %p.help-inline= t('.never_ask_for_personal_email') + .actions + %br + = submit_tag(t('.save_changes'), id: 'Submit', class: 'btn btn-primary') + = render partial: 'authorized_apps' += render partial: 'privacy_text' diff --git a/app/views/project_badges/_badges_table.html.haml b/app/views/project_badges/_badges_table.html.haml index 5f6cc8a27..35129255a 100644 --- a/app/views/project_badges/_badges_table.html.haml +++ b/app/views/project_badges/_badges_table.html.haml @@ -1,16 +1,16 @@ .table-responsive - %table.table - %thead + %table.table.pb-table + %thead.pb-table__head %tr - %th.col-xs-3.no-left-padding= t('project_badges.repo_url') - %th.col-xs-3= t('project_badges.badge_type') - %th.col-xs-3= t('project_badges.badge_url') - %th.col-xs-3= t('project_badges.badge_image') - %tbody + %th.pb-col-repo= t('project_badges.repo_url') + %th.pb-col-type= t('project_badges.badge_type') + %th.pb-col-url= t('project_badges.badge_url') + %th.pb-col-image= t('project_badges.badge_image') + %tbody.pb-table__body - @active_badges.each do |badge| %tr - %td.col-xs-3.no-left-padding= badge.code_location.url - %td.col-xs-3= badge.class.badge_name - %td.col-xs-3= badge.badge_url - %td.col-xs-3 + %td.pb-col-repo= badge.code_location.url + %td.pb-col-type= badge.class.badge_name + %td.pb-col-url= badge.badge_url + %td.pb-col-image = image_tag badge.badge_url, alt: 'Badge not available' diff --git a/app/views/project_badges/_editable_badges_table.html.haml b/app/views/project_badges/_editable_badges_table.html.haml index 395d37747..2a913c36f 100644 --- a/app/views/project_badges/_editable_badges_table.html.haml +++ b/app/views/project_badges/_editable_badges_table.html.haml @@ -1,13 +1,13 @@ -.table-responsive - %table.table - %thead +.pb-table-scroll + %table.table.pb-table + %thead.pb-table__head %tr - %th.col-xs-2.no-left-padding= t('project_badges.repo_url') - %th.col-xs-2= t('project_badges.badge_type') - %th.col-xs-3= t('project_badges.badge_url') - %th.col-xs-3= t('project_badges.badge_image') - %th.col-xs-2= t('project_badges.actions') - %tbody + %th.pb-col-repo= t('project_badges.repo_url') + %th.pb-col-type= t('project_badges.badge_type') + %th.pb-col-url= t('project_badges.badge_url') + %th.pb-col-image= t('project_badges.badge_image') + %th.pb-col-actions= t('project_badges.actions') + %tbody.pb-table__body - @active_badges.each do |badge| = render partial: 'edit_badge', locals: { badge: badge } - display_new_badge = 'display: none' if @project_badge.errors.empty? diff --git a/app/views/project_badges/_new_badge_form.html.haml b/app/views/project_badges/_new_badge_form.html.haml index 6e88c6c45..ccc8b7366 100644 --- a/app/views/project_badges/_new_badge_form.html.haml +++ b/app/views/project_badges/_new_badge_form.html.haml @@ -1,14 +1,14 @@ = form_for @project_badge, url: project_project_badges_path(@project), method: :post, html: { id: 'new_project_badge' } do |f| - %td.col-xs-2.no-left-padding + %td.col-md-2.no-left-padding = f.select :enlistment_id, @enlistments, {}, id: 'select_project_repo', class: 'chzn-select' - error_tag @project_badge, :enlistment_id - %td.col-xs-2 + %td.col-md-2 = f.select :type, @badges, { include_blank: true }, id: 'select_project_badge', class: 'chzn-select badge_image_val selected_badge_val' - error_tag @project_badge, :type - %td.col-xs-3#badge_url_input_container + %td.col-md-3#badge_url_input_container = f.hidden_field :identifier, class: 'badge_url_field' - cii_badge_temp_class = 'hidden' unless @project_badge.type == 'CiiBadge' .cii_badge_url.badge_template{ class: cii_badge_temp_class } @@ -20,8 +20,8 @@ = ENV['TRAVIS_API_BASE_URL'] = text_field_tag('travis_url', @project_badge.identifier, class: 'badge_url_holder') - error_tag(@project_badge, :identifier) unless @project_badge.errors[:type].present? - %td.col-xs-3.badge_image_container - = image_tag @project_badge.try(:badge_url) if @project_badge.identifier - %td.col-xs-2 - %btn.icon-edit.btn.btn-mini.btn-primary.col-xs-12#save_badge= t('project_badges.save_badge') - %btn.icon-edit.btn.btn-mini.btn-refresh.col-xs-12#cancel_badge= t('project_badges.cancel') + %td.col-md-3.badge_image_container + = image_tag @project_badge.try(:badge_url), alt: '' if @project_badge.identifier + %td.col-md-2 + %button.icon-edit.btn.btn-mini.btn-primary.col-md-12#save_badge{ type: 'button' }= t('project_badges.save_badge') + %button.icon-edit.btn.btn-mini.btn-refresh.col-md-12#cancel_badge{ type: 'button' }= t('project_badges.cancel') diff --git a/app/views/project_badges/index.html.haml b/app/views/project_badges/index.html.haml index 426c3b3c6..9da8fac74 100644 --- a/app/views/project_badges/index.html.haml +++ b/app/views/project_badges/index.html.haml @@ -1,15 +1,25 @@ :ruby content_for(:html_title) { t('.page_title', name: @project.name) } + #project_badges_page - %h2.pull-left.title - = link_to t(:settings), settings_project_path(@project) - \: #{t('.title')} + .pb-page__header + .pb-page__title + .pb-page__title-icon + %i.fa.fa-trophy{ 'aria-hidden' => 'true' } + .pb-page__title-text + %h2 + = link_to t(:settings), settings_project_path(@project) + = ": #{t('.title')}" + %p.pb-page__subtitle= t('.listing') + - if logged_in? && @project.edit_authorized? - .badge_table_container + .pb-table-card = render 'editable_badges_table' - .clearfix - - btn_css = 'display: none;' unless @project_badge.errors.empty? - %btn.btn.btn-primary#add_badge_btn{ style: btn_css }= t('.new_badge') + .pb-actions + - btn_css = 'display: none;' unless @project_badge.errors.empty? + %button.pb-btn-new#add_badge_btn{ style: btn_css } + %i.fa.fa-plus{ 'aria-hidden' => 'true' } + = t('.new_badge') - else - .badge_table_container + .pb-table-card = render 'badges_table' diff --git a/app/views/project_licenses/_about.html.haml b/app/views/project_licenses/_about.html.haml index c402c02b9..a48d7b426 100644 --- a/app/views/project_licenses/_about.html.haml +++ b/app/views/project_licenses/_about.html.haml @@ -1,11 +1,14 @@ -%h4.nomargin - %span.soft= t(:about) - = t('.licenses') -.col-md-5 - %ul{ style: 'padding: 15px;' } - %li= t('.help1') - %li= t('.help2') -.col-md-5 - %ul{ style: 'padding: 15px;' } - %li= t('.help3') - %li= t('.help4') +.about-project-basics-card + .card-header + %h4.card-title + %span.soft= t('.about') + = t('.licenses') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + + .card-content + .about-content-wrapper + .col-md-6.margin_left_30 + %ul + %li= t('.help3') + %li= t('.help4') diff --git a/app/views/project_licenses/index.html.haml b/app/views/project_licenses/index.html.haml index 84a480942..f419d0dab 100644 --- a/app/views/project_licenses/index.html.haml +++ b/app/views/project_licenses/index.html.haml @@ -8,35 +8,38 @@ %h2.pull-left = link_to t(:settings), settings_project_path(@project)  : #{t('.licenses')} - = project_analysis_timestamp(@project) -- flash.now[:notice] = t('.no_licenses_for_project') if @project_licenses.empty? += render partial: 'layouts/partials/alert' -.clearfix +#project-license-index-page + .row + .col-md-6 + .oh-card.margin_top_20 + .license-index-card-header + %h3.license-index-card-title + %i.fa.fa-certificate + = t('.declared_licenses') + = show_license_button(@project) -- if @project_licenses.any? - .declared_licenses - .col-md-7.well.col-md-offset-2 - %h4= t('.declared_licenses') - %table{ style: 'width: 100%;' } - %tbody - - @project_licenses.each do |project_license| - %tr.license - %td.col-md-8.padding_one_top= project_license.license ? project_license.license.name : '' - %td.col-md-4.padding_one_top.last - .pull-right + - if @project_licenses.any? + .license-list + - @project_licenses.each do |project_license| + .license-list-item + .license-name + %i.fa.fa-file-text-o + = project_license.license ? project_license.license.name : '' + .license-actions - if @project.edit_authorized? - %a.btn.btn-small.btn-danger{ href: project_license_path(@project, project_license), - data: { method: :delete, confirm: t('.remove_confirm') } } - %i.icon-trash= t '.remove' + %a.btn.btn-sm.btn-danger.btn-remove-license{ href: project_license_path(@project, project_license), + data: { method: :delete, confirm: t('.remove_confirm') } } + %i.fa.fa-trash + = t('.remove') - else - = disabled_button bootstrap_icon('icon-trash', t('.remove')), class: 'btn-small btn-danger' - %tr - %td.padding_one_top{ colspan: '2' }= show_license_button(@project) - .clear + = disabled_button bootstrap_icon('icon-trash', t('.remove')), class: 'btn-sm btn-danger' + - else + .license-empty-state + %i.fa.fa-folder-open-o.license-empty-icon + %p.license-empty-text= t('.no_licenses_for_project') -- else - = show_license_button(@project) - -= yield page_context[:header_title] if page_context[:header_title] -= render partial: 'project_licenses/about' + .col-md-6 + = render partial: 'project_licenses/about' diff --git a/app/views/project_licenses/new.html.haml b/app/views/project_licenses/new.html.haml index a279d5e62..43dd50bc6 100644 --- a/app/views/project_licenses/new.html.haml +++ b/app/views/project_licenses/new.html.haml @@ -3,12 +3,14 @@ page_context[:select_top_menu_nav] = 'select_projects' page_context[:select_footer_nav] = nil content_for :javascript do - js = "$('#edit_project_license').submit(function(e) { \ - if($('.chzn-container-single a.chzn-single').text().trim() == '#{t('.select_placeholder')}') { \ - e.preventDefault(); \ - $('#error').show(); \ - } \ - });" + js = "document.addEventListener('DOMContentLoaded', function() { \ + $('#edit_project_license').submit(function(e) { \ + if($('.chzn-container-single a.chzn-single').text().trim() == '#{t('.select_placeholder')}') { \ + e.preventDefault(); \ + $('#error').show(); \ + } \ + }); \ + });" javascript_tag(js) end @@ -21,34 +23,46 @@  : #{t(:new)} = project_analysis_timestamp(@project) -#error{ style: 'display: none;' } - .alert.alert-info - %i.icon-exclamation-sign - = t('.at_least_one') += render partial: 'layouts/partials/alert' -.well.col-md-7.col-md-offset-2 - = form_for ProjectLicense.new, url: project_licenses_path(@project), - html: { class: 'edit_project', id: 'edit_project_license' } do - %fieldset - %legend= t('.new_declared_license') - .control-group - %label.control-label.required= t('.license') - .controls.chosen#value_select - %select.chzn-select.value-select#value_selector{ name: 'license_id', style: 'width: 524px;', - data: { placeholder: t('.select_placeholder') } } - %option{ value: 'blank' } - - @licenses.each do |license| - %option{ value: license.id }= license.name - %p.clear.help-block - - link = link_to t('.add_it'), new_license_path - != t('.add_it_help', link: link) - - error_tag @license, :license_id +#project-license-new-page + .row + .col-md-6 + .oh-card.margin_top_20 + .license-new-card-header + %h3.license-new-card-title + %i.fa.fa-plus-circle + = t('.new_declared_license') - .actions - - if @project.edit_authorized? - %input.btn.btn-primary.add-license{ type: 'submit', value: t('.add_declared_license') } - - else - = disabled_button t('.add_declared_license'), class: 'btn-primary add-license' + #error{ style: 'display: none;' } + .alert.alert-danger.license-alert + %i.fa.fa-exclamation-circle + = t('.at_least_one') -.clear -= render partial: 'project_licenses/about' + = form_for ProjectLicense.new, url: project_licenses_path(@project), + html: { class: 'edit_project license-new-form', id: 'edit_project_license' } do + .license-field-group + %label.license-field-label.required{ for: 'value_selector' } + = t('.license') + .license-select-wrap + %select.chzn-select#value_selector{ name: 'license_id', + data: { placeholder: t('.select_placeholder') } } + %option{ value: 'blank' } + - @licenses.each do |license| + %option{ value: license.id }= license.name + %p.license-help-text + - link = link_to t('.add_it'), new_license_path + != t('.add_it_help', link: link) + - error_tag @license, :license_id + + .license-form-actions + - if @project.edit_authorized? + %button.btn.btn-primary.btn-add-license{ type: 'submit' } + %i.fa.fa-plus + = t('.add_declared_license') + = link_to t('cancel'), project_licenses_path(@project), class: 'btn btn-default btn-cancel-license' + - else + = disabled_button t('.add_declared_license'), class: 'btn-primary add-license' + + .col-md-6 + = render partial: 'project_licenses/about' diff --git a/app/views/project_tags/_related_project.html.haml b/app/views/project_tags/_related_project.html.haml index cfbe436ad..bd10d7b96 100644 --- a/app/views/project_tags/_related_project.html.haml +++ b/app/views/project_tags/_related_project.html.haml @@ -1,9 +1,8 @@ -.project{ id: "related_project_#{related_project.to_param}" } - %h4.col-md-6.similar_tags_project_name.no_margin_top - = related_project.decorate.icon(:small) - - name = truncate_project_name(related_project.name, 20, link: true) - = link_to h(name), project_path(related_project), title: related_project.name - .col-md-6.show_tags.similar_tags_tagged.tags +.related-project-item.oh-card{ id: "related_project_#{related_project.to_param}" } + .related-project-item__header + .related-project-item__name + = related_project.decorate.icon(:small) + = link_to h(truncate_project_name(related_project.name, 20, link: true)), project_path(related_project), title: related_project.name + .related-project-item__tags.tags - related_project.tags.sort { |a, b| a.name <=> b.name }.each do |tag| %a.tag{ tagname: tag.name, href: 'javascript:void(0);' }= tag.name - .clear_left diff --git a/app/views/project_tags/index.html.haml b/app/views/project_tags/index.html.haml index c91e84e47..1688d5adb 100644 --- a/app/views/project_tags/index.html.haml +++ b/app/views/project_tags/index.html.haml @@ -4,58 +4,65 @@ page_context[:select_footer_nav] = :settings remaining_tags = Tag::MAX_ALLOWED_PER_PROJECT - @project.tags.length -.project_content_title - %h2 - = link_to t(:settings), settings_project_path(@project) -  : #{t('.tags')} -.row#settings_tags - .col-md-6 - .well - %h4= t('.tags_for', name: truncate_project_name(@project.name, 43)) - %div - %p.tags_left!= tags_left(remaining_tags) - %span.tags#current_tags +#project-tags-page + .project-tags-header + %h2.project-tags-title + = link_to t(:settings), settings_project_path(@project) + %span.project-tags-title__sep › + = t('.tags') + + .row + .col-md-6.col-sm-6.col-xs-12 + .oh-card + %h3.project-tags-card-title= t('.tags_for', name: truncate_project_name(@project.name, 43)) + %p.project-tags-left!= tags_left(remaining_tags) + .project-tags-current#current_tags - @project.tags.sort { |a, b| a.name <=> b.name }.each do |tag| - %a{ tagname: tag.name, class: "tag#{' delete tag_remove' if @project.edit_authorized?}" } + %a.project-tag{ tagname: tag.name, class: @project.edit_authorized? ? 'delete tag_remove' : '', href: 'javascript:void(0)', role: 'button', aria: { label: "#{tag.name}#{@project.edit_authorized? ? ' – remove tag' : ''}" } } = tag.name -   - %i.icon-remove + - if @project.edit_authorized? + %i.icon-remove.project-tag__remove %span.hidden{ tagname: tag.name } - = image_tag 'spinner.gif' - .clear   - %form#edit_tags{ project_id: @project.id, project: @project.to_param, - style: remaining_tags > 0 ? '' : 'display: none;', rel: 'tag_edit' } - %p.strong= t('.add_new_tag') - .control-group.nomargin + = image_tag 'spinner.gif', alt: '' + + %form#edit_tags.project-tags-form{ project_id: @project.id, project: @project.to_param, + style: remaining_tags > 0 ? '' : 'display: none;', rel: 'tag_edit' } + %label.project-tags-form__label{ for: 'input_tags' }= t('.add_new_tag') + .project-tags-form__row - if @project.edit_authorized? - %input#input_tags{ type: :text, placeholder: t('.enter_tag_here') } + %input.form-control#input_tags{ type: :text, placeholder: t('.enter_tag_here'), aria: { label: t('.enter_tag_here') } } - else - %input{ type: :text, placeholder: t('.enter_tag_here') } - %p.error.hidden#error   - .control-group + %input.form-control{ type: :text, placeholder: t('.enter_tag_here'), aria: { label: t('.enter_tag_here') }, disabled: true } - if @project.edit_authorized? - %input.btn.btn-primary#submit{ type: 'submit', value: t('.save_tag') } + %input.btn.btn-primary.project-tags-form__submit#submit{ type: 'submit', value: t('.save_tag') } - else - = disabled_button t('.save_tag'), class: 'btn-primary' + = disabled_button t('.save_tag'), class: 'btn-primary project-tags-form__submit' + %p.error.hidden#error   + %span.spinner.hidden= image_tag 'spinner.gif', alt: '' - %span.spinner.hidden= image_tag 'spinner.gif' + .col-md-6.col-sm-6.col-xs-12 + %h3.project-tags-section-title= t('.projects_with_similar_tags') + #related_projects + = render partial: 'related_projects' + .hidden#related_spinner= image_tag 'spinner.gif', alt: '' - .col-md-6 - %h4= t('.projects_with_similar_tags') - #related_projects - = render partial: 'related_projects' - .hidden#related_spinner= image_tag 'spinner.gif' + .about-project-basics-card + .card-header + %h4 + %span.soft= t(:about) + = t('.tags') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down -%h4.nomargin - %span.soft= t(:about) - = t('.tags') -.col-md-5.padding_left_45 - %ul - %li= t('.help1') - %li= t('.help2', max: Tag::MAX_ALLOWED_PER_PROJECT) - %li= t('.help3') -.col-md-5 - %ul - %li= t('.help4') - %li= t('.help5') - %li= t('.help6', max: Tag::MAX_ALLOWED_PER_PROJECT) + .card-content + .about-content-wrapper + .col-md-5.padding_left_45 + %ul + %li= t('.help1') + %li= t('.help2', max: Tag::MAX_ALLOWED_PER_PROJECT) + %li= t('.help3') + .col-md-5 + %ul + %li= t('.help4') + %li= t('.help5') + %li= t('.help6', max: Tag::MAX_ALLOWED_PER_PROJECT) diff --git a/app/views/project_widgets/_footer.html.haml b/app/views/project_widgets/_footer.html.haml index 86f8c2527..52bfff142 100644 --- a/app/views/project_widgets/_footer.html.haml +++ b/app/views/project_widgets/_footer.html.haml @@ -8,7 +8,7 @@ .link_container = link_to project_url(project), target: '_blank', class: 'project_link' do = t('.more') - = image_tag(widget_ohloh_logo_url) + = image_tag(widget_ohloh_logo_url, alt: 'Open Hub') = link_to project_url(project), target: '_blank', class: "project_link #{name_class}", title: project.name do = h(truncate(project.name, length: 50)) if show_name - if oldest_code_set_time diff --git a/app/views/project_widgets/_ohloh_code_footer.html.haml b/app/views/project_widgets/_ohloh_code_footer.html.haml index a5d721bfb..d2fc0daa7 100644 --- a/app/views/project_widgets/_ohloh_code_footer.html.haml +++ b/app/views/project_widgets/_ohloh_code_footer.html.haml @@ -1,5 +1,5 @@ %h2.footer.ohloh_widget_code_footer - = link_to 'http://code.openhub.net', target: '_blank', style: 'margin-right:11px' do + = link_to 'http://code.openhub.net', target: '_blank', rel: 'noopener noreferrer', style: 'margin-right:11px' do = t('.with') - = image_tag widget_ohloh_logo_url, border: 0 + = image_tag widget_ohloh_logo_url, border: 0, alt: 'Open Hub' = t('.code') diff --git a/app/views/project_widgets/basic_stats.html.haml b/app/views/project_widgets/basic_stats.html.haml index 5f303f46e..bb310f9bc 100644 --- a/app/views/project_widgets/basic_stats.html.haml +++ b/app/views/project_widgets/basic_stats.html.haml @@ -28,7 +28,7 @@ = t('.commit_activity') %br = image_tag commits_spark_project_analysis_url(project_id: project.to_param, id: 'latest'), - width: 179, height: 32 + width: 179, height: 32, alt: '' - elsif project.enlistments.any? %p diff --git a/app/views/project_widgets/cocomo.html.haml b/app/views/project_widgets/cocomo.html.haml index a1b55ade0..b15ca27ef 100644 --- a/app/views/project_widgets/cocomo.html.haml +++ b/app/views/project_widgets/cocomo.html.haml @@ -24,7 +24,7 @@ %br $ %input.right#cocomo_salary{ type: 'text', value: widget.salary, size: 7, - onChange: 'update_cocomo();', onKeyUp: 'update_cocomo();' }> + onChange: 'update_cocomo();', onKeyUp: 'update_cocomo();', 'aria-label' => t('.avg') }> = t('.year') %tr.second_row %td.first diff --git a/app/views/project_widgets/factoids.html.haml b/app/views/project_widgets/factoids.html.haml index 3be828352..08fd4a436 100644 --- a/app/views/project_widgets/factoids.html.haml +++ b/app/views/project_widgets/factoids.html.haml @@ -10,18 +10,18 @@ %ul - if analysis.nil? %li - %img{ src: widget_image_url('fact_info.png') } + %img{ src: widget_image_url('fact_info.png'), alt: '' } = t('.not_ready') = link_to t('.progress'), project_enlistments_url(project), target: '_blank' - else - if analysis.main_language %li - %img{ src: widget_image_url('fact_info.png') } + %img{ src: widget_image_url('fact_info.png'), alt: '' } = link_to t('.mostly_written', lang: analysis.main_language.nice_name), languages_summary_project_analysis_url(project, id: 'latest'), target: '_blank' - factoids.reject { |f| f.type.to_s =~ /FactoidDistribution|FactoidStaff/ }.each do |factoid| %li - %img{ src: factoid_image_path(factoid) }> + %img{ src: factoid_image_path(factoid), alt: '' }> = link_to(factoid, project_factoids_url(project, anchor: factoid.type), target: '_blank') = render partial: 'footer', locals: { project: project, show_name: true } diff --git a/app/views/project_widgets/factoids_stats.html.haml b/app/views/project_widgets/factoids_stats.html.haml index a5d8d71c3..b290af478 100644 --- a/app/views/project_widgets/factoids_stats.html.haml +++ b/app/views/project_widgets/factoids_stats.html.haml @@ -16,22 +16,22 @@ %ul - if analysis.main_language %li - %img{ src: image_path('fact_info.png') }> + %img{ src: image_path('fact_info.png'), alt: '' }> = link_to t('.mostly_written', lang: analysis.main_language.nice_name), languages_summary_project_analysis_path(project, id: 'latest'), target: '_blank' - factoids.reject { |f| f.type.to_s =~ /FactoidDistribution|FactoidStaff/ }.each do |factoid| %li - %img{ src: factoid_image_path(factoid) }> + %img{ src: factoid_image_path(factoid), alt: '' }> = link_to(factoid, project_factoids_path(project, anchor: factoid.type), target: '_blank') %li - %img{ src: image_path('fact_info.png') }> + %img{ src: image_path('fact_info.png'), alt: '' }> = link_to t('.active_contributors', count: analysis.headcount), project_contributors_url(widget.project), target: '_blank' %p = t('.time_line') %br = image_tag commits_spark_project_analysis_url(project_id: project.to_param, id: 'latest'), - width: 179, height: 32 + width: 179, height: 32, alt: '' - elsif project.enlistments.any? %p diff --git a/app/views/project_widgets/index.html.haml b/app/views/project_widgets/index.html.haml index 9443edf3d..59079e1c6 100644 --- a/app/views/project_widgets/index.html.haml +++ b/app/views/project_widgets/index.html.haml @@ -4,10 +4,12 @@ - page_context[:select_footer_nav] = :widgets .project_content_title.margin_bottom_10{ style: 'height: 100%' } - %h2= t('.widgets') + %h2 + %strong= t('.widgets') %p = t('.embed') .clearfix - - @widgets.each do |widget| - .project_widget_container - = render partial: 'widget', locals: { widget: widget, type: 'project', ref: 'sample' } + .oh-card + - @widgets.each do |widget| + .project_widget_container + = render partial: 'widget', locals: { widget: widget, type: 'project', ref: 'sample' } diff --git a/app/views/project_widgets/languages.html.haml b/app/views/project_widgets/languages.html.haml index 8a8441844..6597bca03 100644 --- a/app/views/project_widgets/languages.html.haml +++ b/app/views/project_widgets/languages.html.haml @@ -11,7 +11,7 @@ - if analysis.present? - colors = language_percentages.map { |_, _, attr| attr[:color] } .analysis - = image_tag languages_project_analysis_url(project_id: project.to_param, id: 'latest') + = image_tag languages_project_analysis_url(project_id: project.to_param, id: 'latest'), alt: '' %div %table{ width: '155px', cellpadding: '0', cellspacing: '0' } - language_percentages.each do |_, name, attr| diff --git a/app/views/project_widgets/users.html.haml b/app/views/project_widgets/users.html.haml index 1bcc65d01..22c854a56 100644 --- a/app/views/project_widgets/users.html.haml +++ b/app/views/project_widgets/users.html.haml @@ -16,7 +16,7 @@ - grad_style = widget.vars[:style].to_sym == :rainbow ? 'rainbow' : '' .users_with_style = link_to href, { title: title, target: '_blank' }.merge(link) do - = image_tag widget_ohloh_logo_url + = image_tag widget_ohloh_logo_url, alt: 'Open Hub' .info{ class: grad_style, style: "background-color: #{background_color}" } %p.count= project.user_count %p.user= t('.users') @@ -30,4 +30,4 @@ %span.user= t('.users') .non_css_used_link#iuseit= t('.i_use_it') %span.last   - = image_tag widget_ohloh_logo_url + = image_tag widget_ohloh_logo_url, alt: 'Open Hub' diff --git a/app/views/project_widgets/users_logo.html.haml b/app/views/project_widgets/users_logo.html.haml index b957890ff..6e0b8835f 100644 --- a/app/views/project_widgets/users_logo.html.haml +++ b/app/views/project_widgets/users_logo.html.haml @@ -8,6 +8,6 @@ .users_logo = link_to href, title: t('.support', name: project.name), target: '_blank' do - %img.logo{ src: widget_ohloh_logo_url } + %img.logo{ src: widget_ohloh_logo_url, alt: 'Open Hub' } %span{ onmouseover: 'usersLogoMouseOver(this);', onmouseout: 'usersLogoMouseOut(this);' } = t('.i_use_it') diff --git a/app/views/projects/_project_index.html.haml b/app/views/projects/_project_index.html.haml index 7334835b6..8ff27d5f4 100644 --- a/app/views/projects/_project_index.html.haml +++ b/app/views/projects/_project_index.html.haml @@ -1,104 +1,134 @@ - project = project_index -.well.searchable{ id: "project_#{project.id}" } - %h2.title.pull-left= link_to h(project.name.truncate(70)), project_path(project), title: project.name - .pull-right - .compare - - project_compare_button(project, t('.compare')) if defined?(compare) && compare - - if params[:action].to_s != 'check_forge' - .i_use_this - - project_iusethis_button(project) if defined?(i_use_this) && i_use_this - .clear +.project-card.searchable{ id: "project_#{project.id}" } + / Project Header with Compare Option + .project-header + .project-title-section + %h2 + = link_to h(project.name.truncate(70)), project_path(project), title: project.name - .pull-left#inner_content - %a.pull-left.logo{ href: project_path(project), style: 'max-width: 64px;' } - - project_icon(project, :med) + - if defined?(compare) && compare + .compare-section + .compare-checkbox{ data: { project_id: project.to_param } } + %span Compare + %i.fa.fa-square-o - .info.pull-left - %p - - if project.organization + / Project Content + .project-content + .project-meta + - if project.organization + .claimed-by = t('.claimed_by') - = link_to h(truncate(project.organization.name, length: 30)), - organization_path(project.organization), class: 'claimed-by-org', title: project.organization.name - - else -   - + %span + = link_to h(truncate(project.organization.name, length: 30)), + organization_path(project.organization), title: project.organization.name + .analyzed-date - if !project.best_analysis.nil? || @analysis - analysis = (project.best_analysis || @analysis) %i %abbr.date{ title: analysis.updated_on } = t('.analyzed_ago', time: time_ago_in_words(analysis.updated_on)) - else - %span.soft.pull-right - %i= t('.no_analysis_available') + %span.soft + %i= t('.no_analysis_available') - .desc - - if project.description && project.description.strip.size > 340 - = expander(project.description, 300, 340) - - elsif project.description - %span.proj_desc_toggle{ style: 'display: inline', id: "proj_desc_#{project.id}_lg" } + / Main Content - Left Side + .project-main + / Icon and Description + .project-icon-desc + = project.decorate.icon(:med) + + .project-description + - if project.description && project.description.strip.size > 220 + = project.description.truncate(220) + %span.read-more [More] + - elsif project.description = project.description + - else + %span.text-muted= t('.no_description_available') - .stats.pull-left - %p - - if project.best_analysis.empty? - = link_to 0, '#' - - else - - code_total_str = project.best_analysis.code_total.to_human - = link_to code_total_str, languages_summary_project_analysis_path(project, 'latest') - %span= t('.loc') - %p - = link_to number_with_delimiter(project.active_committers), summary_project_contributors_path(project) - %span= t('.current_contributors') - %p - - if !project.best_analysis.nil? && project.best_analysis.last_commit_time - %a{ href: summary_project_commits_path(project) }= time_ago_in_words(project.best_analysis.last_commit_time) - - else - = link_to 0, '#' - %span= t('.since_last_commit') - %p - = link_to number_with_delimiter(project.user_count), users_project_path(project) - %span= t('.users_on_open_hub') + / Language and License + .project-lang-license + .language + - if project.best_analysis.present? && project.best_analysis.main_language + - lang = project.best_analysis.main_language.nice_name + = t('.mostly_written_in', lang: link_to(lang, languages_summary_project_analysis_path(project, 'latest'))).html_safe + - else + %span= t('.not_available') - .reviews-and-pai.pull-left - - project_activity_level_class(project, :twentyfive) - - project_activity_level_text(project, :twentyfive) - .stars!= rating_stars('average_rating_stars', project.rating_average.to_f || 0, mini: true) - .reviews - = link_to pluralize_with_delimiter(project.reviews.count, t('.review')), summary_project_reviews_path(project) - .use_this{ style: 'margin-top: 10px; margin-left: 10px' } - - if logged_in? - = render partial: 'projects/show/i_use_this', locals: { project: project } - - else - .btn.btn-mini.i_use_this_btn{ 'data-target' => '#LoginModal', 'data-toggle' => 'modal' }= t('.i_use_this') - .modal.fade#LoginModal{ 'aria-hidden' => 'true', 'aria-labelledby' => 'LoginModalLabel', :role => 'dialog' } - .modal-dialog.modal-sm - .modal-content{ style: 'width: 398px' } - .modal-header - %button.close{ 'aria-label' => 'Close', 'data-dismiss' => 'modal', :type => 'button' } - %span{ 'aria-hidden' => 'true' } × - %h4.modal-title#LoginModalLabel= t('.login_required') - .model-body - = render 'sessions/sign_in' + .licenses + %span.license-label= t('.licenses') + - if project.licenses.any? + = project.licenses.first(3).collect { |l| l.short_name.truncate(12) }.join(', ') + - else + = t('.no_declared_licenses') + + / Stats - Right Side + .project-stats + + .stats-grid + / Row 1: Lines of Code | Activity Indicator (spans 2 rows) + .stat-item + %span.stat-value + - if project.best_analysis.empty? + 0 + - else + = project.best_analysis.code_total.to_human + %span.stat-label= t('.loc') + + .activity-indicator + .activity-icon + - project_activity_level_class(project, :twentyfive) + .activity-label + = project_activity_text(project, true) + + / Row 2: Contributors + .stat-item + %span.stat-value= number_with_delimiter(project.active_committers) + %span.stat-label= t('.current_contributors') + + / Row 3: Last Commit | Star Rating + .stat-item + %span.stat-value + - if !project.best_analysis.nil? && project.best_analysis.last_commit_time + = time_ago_in_words(project.best_analysis.last_commit_time) + - else + 0 + %span.stat-label= t('.since_last_commit') + + .rating-section + .stars + - rating = project.rating_average.to_f + - full_stars = rating.floor + - has_half_star = (rating - full_stars) >= 0.5 + - 5.times do |i| + - if i < full_stars + %i.fa.fa-star.star.filled + - elsif has_half_star && i <= full_stars + %i.fa.fa-star-half-o.star.half + - else + %i.fa.fa-star.star.empty + .review-count + = link_to pluralize_with_delimiter(project.reviews.count, t('.review')), summary_project_reviews_path(project) + + / Row 4: Users on Open Hub | I Use This Button + .stat-item + %span.stat-value= number_with_delimiter(project.user_count) + %span.stat-label= t('.users_on_open_hub') - .add-info - .main_language.pull-left - - if project.best_analysis.present? && project.best_analysis.main_language - - lang = project.best_analysis.main_language.nice_name - = link_to t('.mostly_written_in', lang: lang), languages_summary_project_analysis_path(project, 'latest') - - else - %span= t('.mostly_written_lang_not_available') - .licenses.pull-right - = link_to t('.licenses'), project_licenses_path(project) - - if project.licenses.any? - = project.licenses.first(3).collect { |l| l.short_name.truncate(12) }.join(', ') - - else - %span= t('.no_declared_licenses') - .clear_both + .use-this-button + - if logged_in? + = render partial: 'projects/show/i_use_this', locals: { project: project } + - else + %button.btn-use-this{ 'data-target' => '#LoginModal', 'data-toggle' => 'modal' } + = t('.i_use_this') - .tags - - unless project.tag_list.blank? - - tags = project.tag_list.split(' ') - = tag_icon_link(project) - = tag_links(tags, 12) - - if tags.length > 12 - = link_to t('.n_more', n: (tags.length - 12)), project_path(project) + / Tags + .project-tags + - unless project.tag_list.blank? + %span.tags-label= t('.tags') + - tags = project.tag_list.split(' ') + - tags.first(8).each do |tag| + - tag = tag.fix_encoding_if_invalid + = link_to html_escape(tag), tags_path(names: tag), class: 'tag' + - if tags.length > 8 + %span.more-tags= t('.n_more', n: tags.length - 8) \ No newline at end of file diff --git a/app/views/projects/_similar_project.html.haml b/app/views/projects/_similar_project.html.haml index b1dfc1dbf..86af574b4 100644 --- a/app/views/projects/_similar_project.html.haml +++ b/app/views/projects/_similar_project.html.haml @@ -3,28 +3,27 @@ compare_title = t('.compare_title', name_1: project.name, name_2: @project.name) languages_summary_path = languages_summary_project_analysis_path(project, id: 'latest') -.col-md-4.no_padding - .col-md-3.no_padding - .pull-left= link_to project.decorate.icon(:med), project_path(project), title: h(project.description) - .col-md-9.no_padding - %h4.nomargin.no_margin_top - = link_to h(project.name), project_path(project) -   - = link_to t('.compare'), compare_url, style: 'font-size: 12px', title: compare_title - .nomargin{ style: 'position: relative' } - - project_activity_level_class(project, :fifteen) - %span{ class: project_activity_level_text_class(:fifteen) } - = project_activity_text(project, true) +.similar-card.oh-card + .similar-card-header + .similar-card-icon + = link_to project.decorate.icon(:med), project_path(project), title: h(project.description) + .similar-card-meta + %h4.similar-card-title + = link_to h(project.name), project_path(project), class: 'similar-card-name' + = link_to t('.compare'), compare_url, class: 'similar-compare-btn', title: compare_title + .similar-card-activity + - project_activity_level_class(project, :fifteen) + %span{ class: project_activity_level_text_class(:fifteen) } + = project_activity_text(project, true) + .similar-card-body - if project.best_analysis.present? - %p.nomargin - = t('.most_written') - = link_to project.best_analysis.main_language&.nice_name, languages_summary_path - %p.nomargin - = t('.licence') - - if project.licenses.any? - = project.licenses.map { |license| link_to h(license.short_name), license_path(license) }.join(', ').html_safe - - else - = t('.unknown') - .margin_top_10 - %br -   + .similar-card-row + %span.similar-card-label= t('.most_written') + = link_to project.best_analysis.main_language&.nice_name, languages_summary_path, class: 'similar-card-value-link' + .similar-card-row + %span.similar-card-label= t('.licence') + %span.similar-card-value + - if project.licenses.any? + = project.licenses.map { |license| link_to h(license.short_name), license_path(license), class: 'similar-card-value-link' }.join(', ').html_safe + - else + %span.similar-muted= t('.unknown') diff --git a/app/views/projects/check_forge.html.haml b/app/views/projects/check_forge.html.haml index 09b945b9c..dc2d1c5a7 100644 --- a/app/views/projects/check_forge.html.haml +++ b/app/views/projects/check_forge.html.haml @@ -3,102 +3,115 @@ content_for(:html_title) { t('.page_title', name: @project.name.blank? ? 'New Project' : @project.name) } page_context[:select_top_menu_nav] = 'select_projects' -%h1.margin_bottom_20= t('projects.new.call_to_action') -.row - .col-md-12 - %div{ style: 'padding: 0 15px;' } - .well - = form_for @project, as: :project, url: projects_path, html: { class: 'styled new_project' } do |f| - %fieldset - %legend= t('.create_a_new_project') - .control-group.nomargin - %label.control-label.required= t('.project_name') - .controls - %input.col-md-5#project_name{ name: 'project[name]', type: 'text', value: @project.name } - - error_tag @project, :name - %p.help-block!= t('.project_name_help', link: link_to(t('.contact_us'), blog_url_for(:contact_form))) - .control-group.nomargin - %label.control-label.required= t('.project_source_code') - .controls.chosen#value_select - - scm_type = @project.code_location_object&.scm_type +%h1.margin_bottom_20.margin_left_30.check-forge-title= t('projects.new.call_to_action') + +#project-edit-page + .row.margin_left_30.check-forge-row + .oh-card.col-md-10.col-sm-12.col-xs-12 + = form_for @project, as: :project, url: projects_path, html: { class: 'styled new_project' } do |f| + %fieldset.margin_left_20.check-forge-fieldset + %legend= t('.create_a_new_project') + + .control-group + %label.control-label.required{ for: 'project_name' }= t('.project_name') + .controls + %input.form-control#project_name{ name: 'project[name]', type: 'text', value: @project.name } + - error_tag @project, :name + %p.help-block!= t('.project_name_help', link: link_to(t('.contact_us'), blog_url_for(:contact_form))) + + .control-group + %label.control-label.required{ for: 'repository_type' }= t('.project_source_code') + .controls.chosen#value_select + - scm_type = @project.code_location_object&.scm_type + .source-code-fields = select_tag :scm_type, scm_options_for_select(scm_type), name: 'project[enlistments_attributes][0][code_location_attributes][scm_type]', - class: 'chzn-select value-select', id: 'repository_type', style: 'width: 100px' + class: 'chzn-select value-select form-control', id: 'repository_type' + %label.control-label{ for: 'url' }= t('.repository_url') = text_field_tag :url, @project.code_location_object&.url, - style: 'width: 265px; margin-bottom: 1px', + class: 'form-control', name: 'project[enlistments_attributes][0][code_location_attributes][url]' + %label.control-label{ for: 'branch' }= t('.branch') = text_field_tag :branch, @project.code_location_object&.branch || 'main', - style: 'width: 105px; margin-bottom: 1px', + class: 'form-control', name: 'project[enlistments_attributes][0][code_location_attributes][branch]' - .clear_left - - error_tag @project, 'enlistments.base' - .control-group - .controls - %label.checkbox.control-label{ style: 'padding: 0;' } - = f.check_box :managed_by_creator, choice: t('.i_manage_this_project'), class: 'col' + .clear_left + - error_tag @project, 'enlistments.base' + + .control-group + .controls + .checkbox + %label{ for: 'project_managed_by_creator' } + = f.check_box :managed_by_creator, id: 'project_managed_by_creator' = t('.i_manage_this_project') - %p.help-block - = t('.i_manage_this_project_help1') - %br - = t('.i_manage_this_project_help2') - .control-group - %label.control-label= t('.homepage_page') - .controls - = f.text_field :url, class: 'col-md-5' - - error_tag @project, :url - %p.help-block= t('.homepage_page_help') - .control-group - %label.control-label= t('.download_page') - .controls - = f.text_field :download_url, class: 'col-md-5' - - error_tag @project, :download_url - %p.help-block= t('.download_page_help') - .control-group - %label.control-label= t('.add_license') - .controls - %input.license.col-md-5#add_license{ type: 'text' } - %p.help-block - = t('.add_license_help1') - %br - = t('.add_license_help2') - .control-group - %label.control-label= t('.chosen_licenses') - .controls.chosen_licenses - .no-license.inset= t('.none') + %p.help-block + = t('.i_manage_this_project_help1') + %br + = t('.i_manage_this_project_help2') + + .control-group + %label.control-label{ for: 'project_url' }= t('.homepage_page') + .controls + = f.text_field :url, class: 'form-control' + - error_tag @project, :url + %p.help-block= t('.homepage_page_help') + + .control-group + %label.control-label{ for: 'project_download_url' }= t('.download_page') + .controls + = f.text_field :download_url, class: 'form-control' + - error_tag @project, :download_url + %p.help-block= t('.download_page_help') - .license-cell.col-md-5.no_margin_left.license-template - .col-md-6.license_name - .col-md-5.pull-right{ style: 'margin: 0 20px 20px 0' } - %a.btn.btn-danger.btn-mini.remove_license.col{ href: 'javascript:' } - %i.icon-trash> - = t('.remove') - .clearfix - .control-group.clear_left - %label.control-label.required= t('.url') - .controls - .input-prepend - %span.add-on= 'http://www.openhub.net/p/' - = f.text_field :vanity_url, class: 'check-availability', autocomplete: 'off', - style: 'width: 188px;', + .control-group + %label.control-label{ for: 'add_license' }= t('.add_license') + .controls + %input.license.form-control#add_license{ type: 'text' } + %p.help-block + = t('.add_license_help1') + %br + = t('.add_license_help2') + + .control-group + %label.control-label= t('.chosen_licenses') + .controls.chosen_licenses + .no-license.inset= t('.none') + .license-cell.col-md-5.col-xs-12.no_margin_left.license-template + .col-md-6.col-xs-8.license_name + .col-md-5.col-xs-4.license-remove-btn-wrap + %a.btn.btn-danger.btn-mini.remove_license.col{ href: 'javascript:' } + %i.icon-trash> + = t('.remove') + + .clearfix + .control-group.clear_left + %label.control-label.required{ for: 'project_vanity_url' }= t('.url') + .controls + .input-prepend.project-url-prepend + %span.add-on http://www.openhub.net/p/ + = f.text_field :vanity_url, class: 'form-control check-availability', autocomplete: 'off', 'data-original-value' => Project.find_by(id: @project).try(:vanity_url), 'data-ajax-path' => project_check_availabilities_path, 'data-preview-base-url' => projects_url - = render 'shared/availability_preview' - - error_tag @project, :vanity_url, class: 'error vanity_url' - %p.help-block - = t('.url_help1') - %br - = t('.url_help2') - .control-group - %label.control-label= t('.description') - .controls - = find_and_preserve(f.text_area(:description, max_length: 800, style: 'width: 376px; height: 200px;')) - - error_tag @project, :description - %p.help-block - = t('.description_help1') - %br - = t('.description_help2') - .actions.clear_left - %input.btn.btn-primary{ type: 'submit', value: t('.submit'), 'data-disable-with' => t('.processing') } + = render 'shared/availability_preview' + - error_tag @project, :vanity_url, class: 'error vanity_url' + %p.help-block + = t('.url_help1') + %br + = t('.url_help2') + + .control-group + %label.control-label{ for: 'project_description' }= t('.description') + .controls + = find_and_preserve(f.text_area(:description, max_length: 800, class: 'form-control edit-description')) + - error_tag @project, :description + %p.help-block + = t('.description_help1') + %br + = t('.description_help2') + + .actions.clear_left + %input.btn.btn-primary{ type: 'submit', value: t('.submit'), 'data-disable-with' => t('.processing') } + diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index cd8af0dac..1b3f7f476 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -5,54 +5,59 @@ %h2.float_left = link_to t('.settings'), settings_project_path(params[:id]) \ : - = t('.basics') - = project_analysis_timestamp(@project) + %strong= t('.basics') -.row - .well.col-md-10.col-md-offset-1 - = form_for @project, as: :project, url: project_path(params[:id]), html: { method: :put, - class: 'center-block edit_project', - style: 'width:710px;' } do |f| - %fieldset - .control-group - %label.control-label.required= t('.project_name') - .controls - %input{ name: 'project[name]', id: 'project_name', type: 'text', value: @project.name, - style: 'width: 100%;' } - - error_tag @project, :name - .clearfix +#project-edit-page + .row.margin_left_5 + .oh-card.col-md-8 + = form_for @project, as: :project, url: project_path(params[:id]), html: { method: :put, + class: 'edit_project' } do |f| + %fieldset + .control-group + %label.control-label.required{ for: 'project_name' }= t('.project_name') + .controls + %input.form-control{ name: 'project[name]', id: 'project_name', type: 'text', value: @project.name } + - error_tag @project, :name + .clearfix - .control-group - %label.control-label.required= t('.oh_project_url') - .controls - .input-prepend - %span.add-on http://www.openhub.net/p/ - = f.text_field :vanity_url, class: 'check-availability', autocomplete: 'off', - style: 'width:514px;', - 'data-original-value' => Project.find_by(id: @project).try(:vanity_url), - 'data-ajax-path' => project_check_availabilities_path, - 'data-preview-base-url' => projects_url - - error_tag @project, :vanity_url, class: 'error vanity_url' - = render 'shared/availability_preview' - .clearfix + .control-group + %label.control-label.required{ for: 'project_vanity_url' }= t('.oh_project_url') + .controls + .input-prepend.project-url-prepend + %span.add-on http://www.openhub.net/p/ + = f.text_field :vanity_url, class: 'form-control check-availability', autocomplete: 'off', + 'data-original-value' => Project.find_by(id: @project).try(:vanity_url), + 'data-ajax-path' => project_check_availabilities_path, + 'data-preview-base-url' => projects_url + - error_tag @project, :vanity_url, class: 'error vanity_url' + = render 'shared/availability_preview' + .clearfix - .control-group - %label.control-label= t('.description') - .controls - = find_and_preserve(f.text_area(:description, max_length: 800, style: 'width: 100%; height: 150px;')) - - error_tag @project, :description - .clearfix + .control-group + %label.control-label{ for: 'project_description' }= t('.description') + .controls + = find_and_preserve(f.text_area(:description, max_length: 800, class: 'form-control edit-description')) + - error_tag @project, :description + .clearfix - .actions - - if logged_in? && @project.edit_authorized? - %input.btn.btn-primary.save{ type: 'submit', value: t('.save_changes') } - - else - = disabled_button t('.save_changes'), class: 'btn-primary save' -%h4.nomargin - %span.soft= t('.about') - = t('.basics') -.col-md-6 - %ul{ style: 'padding-left: 30px;' } - %li!= t('.about1', link: link_to(t('.contact_us'), blog_url_for(:contact_form))) - %li= t('.about2') - %li= t('.about3') + .actions + - if logged_in? && @project.edit_authorized? + %input.btn.btn-primary.save{ type: 'submit', value: t('.save_changes') } + - else + = disabled_button t('.save_changes'), class: 'btn-primary save' + + .about-project-basics-card + .card-header + %h4.card-title + %span.soft= t('.about') + = t('.basics') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + + .card-content + .about-content-wrapper + .col-md-6 + %ul + %li!= t('.about1', link: link_to(t('.contact_us'), blog_url_for(:contact_form))) + %li= t('.about2') + %li= t('.about3') diff --git a/app/views/projects/estimated_cost.html.haml b/app/views/projects/estimated_cost.html.haml index 1cd07df8a..3e4e21cfd 100644 --- a/app/views/projects/estimated_cost.html.haml +++ b/app/views/projects/estimated_cost.html.haml @@ -7,19 +7,16 @@ man_years[:logic_man_years] = analysis.man_years_from_loc(analysis.logic_total) man_years[:markup_man_years] = analysis.man_years_from_loc(analysis.markup_total) man_years[:build_man_years] = analysis.man_years_from_loc(analysis.build_total) - cocomo_link = link_to(t('.basic_cocomo_model'), 'http://en.wikipedia.org/wiki/COCOMO') + cocomo_link = link_to(t('.basic_cocomo_model'), 'http://en.wikipedia.org/wiki/COCOMO', target: '_blank', rel: 'noopener noreferrer') #cocomo_page .project_content_title %h2.float_left= t('.estimated_cost') - = project_analysis_timestamp(@project) .clearfix .col-md-12 - if !analysis.empty? .center-block - .col-md-1 -   .col-md-6 - js = "var project_analysis = #{man_years.to_json};" %script{ type: 'text/javascript' } @@ -27,37 +24,37 @@ \ #{js.html_safe} \ //]]> - %form.cocomo-form.nomargin + %form.cocomo-form.oh-card %fieldset{ style: 'margin-top: -10px;' } - %h4.caption= t('.project_cost_calculator') - .control-group.col-md-4 - %label.control-label= t('.include') + %h4= t('.project_cost_calculator') + .control-group.col-md-6.col-sm-6 + %label.control-label{ for: 'cocomo_loc_dropdown' }= t('.include') .controls.chosen#value_select - %select.col-md-8.chzn-select.value-select#cocomo_loc_dropdown + %select.col-md-8.col-sm-8.chzn-select.value-select#cocomo_loc_dropdown %option.selected{ value: analysis.code_total + analysis.markup_total + analysis.build_total } = t('.all_code') %option{ value: analysis.logic_total }= t('.logic_code_only') %option{ value: analysis.markup_total }= t('.markup_only') %option{ value: analysis.build_total }= t('.build_scripts_only') - .control-group.col-md-8 - %label.control-label= t('.average_salary') + .control-group.col-md-8.col-sm-8 + %label.control-label{ for: 'cocomo_salary' }= t('.average_salary') .controls .input-prepend.input-append %span.add-on $ %input.margin-hack.span2#cocomo_salary{ type: 'text', value: Analysis::AVG_SALARY } %span.add-on .00 - .control-group.col-md-4 + .control-group.col-md-4.col-sm-4 %label.control-label= t('.codebase_size') .controls %span#cocomo_loc= number_with_delimiter(analysis.code_total) = t('.lines') - .control-group.col-md-8 + .control-group.col-md-8.col-sm-8 %label.control-label= t('.estimated_effort') .controls %span#cocomo_years= analysis.man_years.round = t('.person_years') - .control-group.col-md-4 + .control-group.col-md-4.col-sm-4 %label.control-label= t('.estimated_cost') .controls $ @@ -67,29 +64,37 @@ %span{ style: 'margin-left:20px;' } *Using the #{cocomo_link} /.well.float_right.estimated_cost_well - .well.col-md-4 - %h4{ style: 'margin-bottom:0.8em;' }= t('.too_high') - %p!= t('.help1') - - code_location_link = link_to t('.code_locations'), project_enlistments_path(@project) - %p!= t('.help2', link: code_location_link) - %ul.nomargin - %li= t('.help3') - %li= t('.help4') + .oh-card.col-md-6 + %h4= t('.too_high') + .margin_left_15 + %p!= t('.help1') + - code_location_link = link_to t('.code_locations'), project_enlistments_path(@project) + %p!= t('.help2', link: code_location_link) + %ul.nomargin + %li= t('.help3') + %li= t('.help4') - else = render partial: 'projects/show/no_analysis_summary' - .col-md-12.about - %h4.nomargin - %span.soft= t('.about') - = t('.cost_estimates') - .row - .col-md-5 - %ul - %li= t('.about1') - %li!= t('.about2', link: cocomo_link) - %li.coefficients= t('.about3') - .col-md-5 - %ul - %li= t('.about4') - %li= t('.about5') - %li= t('.about6') + .col-md-12 + .about-project-basics-card + .card-header + %h4.card-title + %span.soft= t('.about') + = t('.cost_estimates') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + + .card-content + .about-content-wrapper + .col-md-5 + %ul + %li= t('.about1') + %li!= t('.about2', link: cocomo_link) + %li.coefficients= t('.about3') + .col-md-5 + %ul + %li= t('.about4') + %li= t('.about5') + %li= t('.about6') + diff --git a/app/views/projects/index.html.haml b/app/views/projects/index.html.haml index 93a0e6eab..8708f2fa5 100644 --- a/app/views/projects/index.html.haml +++ b/app/views/projects/index.html.haml @@ -2,20 +2,32 @@ - content_for(:html_title) { @query ? t('.page_title_query', query: @query) : t('.page_title') } - page_context[:select_top_menu_nav] = 'select_projects' - content_for :session_projects_banner do - .navbar.container#sp_menu + #sp_menu = render 'session_projects/menu' -#projects_index_header - %h1.pull-left= t('.projects') - .pull-right.margin_top_20 - = link_to t('.browse_projects'), tags_path, class: 'btn btn-light btn-mini' - = link_to t('.add_new_project'), new_project_path, class: 'btn btn-light btn-mini' +#projects_index_page + .projects-container + / Header Section with Actions + .projects-header + %h1= t('.projects') + .header-actions + = link_to tags_path, class: 'btn-browse-tags' do + %i.fa.fa-tag + = t('.browse_projects') + = link_to new_project_path, class: 'btn-add-project' do + %i.fa.fa-plus + = t('.add_new_project') -.clearfix   + / Header Search with Dropdown + = render 'shared/search' -= render partial: 'shared/search_dingus', locals: { collection: @projects, sort_context: :projects } -.clear + / Search and Filter Bar + = render 'shared/search_dingus', collection: @projects, sort_context: :projects -#projects_index_list - = render partial: 'project_index', collection: @projects, locals: { compare: true, show_active_committers: true } - = will_paginate @projects + / Projects List + #projects_index_list + = render partial: 'project_index', collection: @projects, locals: { compare: true, show_active_committers: true } + + / Modern Pagination + = render 'shared/modern_pagination', collection: @projects + \ No newline at end of file diff --git a/app/views/projects/index_managed.html.haml b/app/views/projects/index_managed.html.haml index 3ceacc08a..494a607c4 100644 --- a/app/views/projects/index_managed.html.haml +++ b/app/views/projects/index_managed.html.haml @@ -8,4 +8,4 @@ = render partial: 'shared/search_dingus', locals: { collection: @projects, sort_context: :projects } = render partial: 'project_index', collection: @projects, locals: { i_use_this: true, compare: false, show_active_committers: false } - = will_paginate @projects + = render 'shared/modern_pagination', collection: @projects diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 18849d4a0..dd2527e1c 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -2,28 +2,31 @@ - page_context[:select_top_menu_nav] = 'select_projects' - if ApiAccess.available? - %h1.margin_bottom_20= t('.call_to_action') - .row - .col-md-12 - = form_tag check_forge_projects_path, class: 'styled new_project' do - .clear   - .col-md-9 - %p= t('.help1') - %p= t('.help2') - .col-md-9 - %label= t('.codelocation_label') - .col-md-9 - %input.span9.field{ name: 'codelocation', type: 'text' } - .col-md-9 - = t('.examples') - .inset - %p - %code!= t('.example1') - %p - %code!= t('.example2') - .col-md-9 - .pull-right - %input.span2.btn.btn-primary{ type: 'submit', value: t('.next') } + #project-new-page + %h1.margin_bottom_20.margin_left_30.project-new-title= t('.call_to_action') + .row.margin_left_30.project-new-row + .oh-card.col-md-8.col-sm-10.col-xs-12 + = form_tag check_forge_projects_path, class: 'styled new_project' do + %fieldset + .project-new-help + %p= t('.help1') + %p.margin_bottom_0= t('.help2') + + .control-group.margin_top_20 + %label.control-label{ for: 'codelocation' }= t('.codelocation_label') + .controls + %input.form-control#codelocation{ name: 'codelocation', type: 'text', + placeholder: 'https://github.com/owner/repo' } + + .project-new-examples + %p.project-new-examples-label= t('.examples') + .project-new-example-item + %code!= t('.example1') + .project-new-example-item + %code!= t('.example2') + + .actions.margin_top_20 + %input.btn.btn-primary{ type: 'submit', value: t('.next') } - else .padding_one_top = render 'shared/api_outage' diff --git a/app/views/projects/settings.html.haml b/app/views/projects/settings.html.haml index 3f99b83b4..4d0830e9c 100644 --- a/app/views/projects/settings.html.haml +++ b/app/views/projects/settings.html.haml @@ -3,91 +3,94 @@ page_context[:select_top_menu_nav] = 'select_projects' page_context[:select_footer_nav] = :settings -%div - %h2.pull-left= t('.title') - = render 'shared/analysis_timestamp', analysis: @project.best_analysis, project: @project +.settings-page-header + .settings-page-header-row + .settings-page-icon + %i.fa.fa-cog + %h2.settings-page-h2= t('.title') + %p.settings-page-description= t('.configure_desc', name: @project.name) -.clearfix.margin_bottom_20   +.settings-grid + %a.settings_module{ href: edit_project_path(@project) } + .settings-icon-wrapper.gradient-purple + %i.fa.fa-cog + .settings-text + %h4= t('.project_basis') + %span.settings_description= t('.project_basis_desc') -.row.margin_bottom_30 - .col-md-4.settings_module - %h4.nomargin - = link_to t('.project_basis'), edit_project_path(@project) - %span.pull-left - %a.module_icon.basics{ href: edit_project_path(@project) } - %span.pull-left.settings_description= t('.project_basis_desc') - .col-md-4.settings_module - %h4.nomargin - = link_to t('.code_location'), project_enlistments_path(@project) - %span.pull-left - %a.module_icon.code_locations{ href: project_enlistments_path(@project) } - %span.pull-left.settings_description= t('.code_location_desc') + %a.settings_module{ href: project_enlistments_path(@project) } + .settings-icon-wrapper.gradient-blue + %i.fa.fa-map-marker + .settings-text + %h4= t('.code_location') + %span.settings_description= t('.code_location_desc') - .col-md-4.settings_module - %h4.nomargin - = link_to t('.aliases'), project_aliases_path(@project) - %span.pull-left - %a.module_icon.aliases{ href: project_aliases_path(@project) } - %span.pull-left.settings_description= t('.aliases_desc') + %a.settings_module{ href: project_aliases_path(@project) } + .settings-icon-wrapper.gradient-teal + %i.fa.fa-users + .settings-text + %h4= t('.aliases') + %span.settings_description= t('.aliases_desc') -.row.margin_bottom_35 - .col-md-4.settings_module - %h4.nomargin - = link_to t('.logo'), new_project_logos_path(@project) - %span.pull-left - %a.module_icon.logo{ href: new_project_logos_path(@project) } - %span.pull-left.settings_description= t('.logo_desc') + %a.settings_module{ href: new_project_logos_path(@project) } + .settings-icon-wrapper.gradient-pink + %i.fa.fa-picture-o + .settings-text + %h4= t('.logo') + %span.settings_description= t('.logo_desc') - .col-md-4.settings_module - %h4.nomargin - = link_to t('.links'), project_links_path(@project) - %span.pull-left - %a.module_icon.links{ href: project_links_path(@project) } - %span.pull-left.settings_description= t('.links_desc') - .col-md-4.settings_module - %h4.nomargin - = link_to t('.permissions'), permissions_project_path(@project) - %span.pull-left - %a.module_icon.permissions{ href: permissions_project_path(@project) } - %span.pull-left.settings_description= t('.permissions_desc') + %a.settings_module{ href: project_links_path(@project) } + .settings-icon-wrapper.gradient-orange + %i.fa.fa-link + .settings-text + %h4= t('.links') + %span.settings_description= t('.links_desc') -.row.margin_bottom_50 - .col-md-4.settings_module - %h4.nomargin - = link_to t('.licenses'), project_licenses_path(@project) - %span.pull-left - %a.module_icon.licenses{ href: project_licenses_path(@project) } - %span.pull-left.settings_description= t('.licenses_desc') - .col-md-4.settings_module - %h4.nomargin - = link_to t('.tags'), project_tags_path(@project) - %span.pull-left - %a.module_icon.tags{ href: project_tags_path(@project) } - %span.pull-left.settings_description= t('.tags_desc') - .col-md-4.settings_module - %h4.nomargin - = link_to t('.edits'), project_edits_path(@project) - %span.pull-left - %a.module_icon.history{ href: project_edits_path(@project) } - %span.pull-left.settings_description= t('.edits_desc') + %a.settings_module{ href: permissions_project_path(@project) } + .settings-icon-wrapper.gradient-red + %i.fa.fa-lock + .settings-text + %h4= t('.permissions') + %span.settings_description= t('.permissions_desc') -.row.margin_bottom_30 - .col-md-4.settings_module - %h4.nomargin - = link_to t('.managers'), project_managers_path(@project) - %span.pull-left - %a.module_icon.managers{ href: project_managers_path(@project) } - %span.pull-left.settings_description= t('.managers_desc') + %a.settings_module{ href: project_licenses_path(@project) } + .settings-icon-wrapper.gradient-green + %i.fa.fa-shield + .settings-text + %h4= t('.licenses') + %span.settings_description= t('.licenses_desc') - .col-md-4.settings_module - %h4.nomargin - = link_to t('.news'), project_rss_subscriptions_path(@project) - %span.pull-left - %a.module_icon.news{ href: project_rss_subscriptions_path(@project) } - %span.pull-left.settings_description= t('.news_desc') - .col-md-4.settings_module - %h4.nomargin - = link_to t('.badges'), project_project_badges_path(@project) - %span.pull-left - %a.module_icon.badges{ href: project_project_badges_path(@project) } - %span.pull-left.settings_description= t('.badges_desc') + %a.settings_module{ href: project_tags_path(@project) } + .settings-icon-wrapper.gradient-yellow + %i.fa.fa-tag + .settings-text + %h4= t('.tags') + %span.settings_description= t('.tags_desc') + + %a.settings_module{ href: project_edits_path(@project) } + .settings-icon-wrapper.gradient-violet + %i.fa.fa-history + .settings-text + %h4= t('.edits') + %span.settings_description= t('.edits_desc') + + %a.settings_module{ href: project_managers_path(@project) } + .settings-icon-wrapper.gradient-sky + %i.fa.fa-user-md + .settings-text + %h4= t('.managers') + %span.settings_description= t('.managers_desc') + + %a.settings_module{ href: project_rss_subscriptions_path(@project) } + .settings-icon-wrapper.gradient-indigo + %i.fa.fa-rss + .settings-text + %h4= t('.news') + %span.settings_description= t('.news_desc') + + %a.settings_module{ href: project_project_badges_path(@project) } + .settings-icon-wrapper.gradient-amber + %i.fa.fa-trophy + .settings-text + %h4= t('.badges') + %span.settings_description= t('.badges_desc') diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index f6a029249..df03ce425 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,7 +1,6 @@ :ruby content_for(:html_title) { t('.page_title', name: @project.name) } page_context[:select_top_menu_nav] = 'select_projects' - page_context[:select_footer_nav] = :project_summary - content_for :twitter_card do %meta{ content: 'summary', name: 'twitter:card' } @@ -12,75 +11,345 @@ %meta{ content: @project.logo.attachment.url(:med), name: 'twitter:image' } %meta{ content: 'https://www.openhub.net', name: 'twitter:domain' } -%div.separator-div - = project_analysis_timestamp(@project) - -.clear -= render partial: 'projects/show/is_a_duplicate', locals: { project: @project } - -.row.row-eq-height.margin_top_two.project_row - .col-md-4.project_summary_container - .well - %h4.text-left= t('.project_summary') - - size_breach = project_description_size_breached?(@project) - %section#project_summary{ itemprop: 'description', class: ('vertical_scroll_bar' if size_breach) } - - if @project.description.present? - = simple_format @project.description.strip_tags - - else - = t('.no_description') - = link_to t('.add_description'), edit_project_path(@project) - - %section#project_tags{ itemscope: '', itemtype: 'http://schema.org/CreativeWork' } - %h4.title= tag_icon_link(@project) - %p.tags - %span - - tags = @project.tag_list.split(' ') - - if tags.any? - = tag_links(tags) +.project-show-redesign{ itemscope: '', itemtype: 'http://schema.org/SoftwareSourceCode' } + + .project-content + / Stats Grid - 3 columns (Lines of Code, Contributors, Total Commits) + .stats-grid + .stat-card + .stat-value + - if @analysis.present? + = number_with_delimiter(@analysis.code_total, delimiter: ',') + - else + N/A + .stat-label= t('.lines_of_code') + + .stat-card + .stat-value + - if @analysis.present? + = number_with_delimiter(@analysis.committers_all_time) + - else + 0 + .stat-label= t('.contributors') + + .stat-card + .stat-value + - if @analysis.present? + = number_with_delimiter(@analysis.commit_count) + - else + 0 + .stat-label= t('.total_commits') + + / Desktop: 3-column grid (Project Summary, In a Nutshell, Quick Reference) + .three-col-grid + / Project Summary + .gradient-card + .card-content + %h3= t('.project_summary') + - if @project.description.present? + = simple_format(@project.description.strip_tags) + - else + %p + = t('.no_description') + = link_to t('.add_description'), edit_project_path(@project) + + - if @project.is_a_duplicate + %p.note + = t('.note') + There are two distinct + = link_to 'Mozilla Core', '#' + and + = link_to 'Mozilla Chrome', '#' + projects for complete coverage. + + %section#project_tags + %h4 + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", style: "width: 1rem; height: 1rem; display: inline-block; vertical-align: text-top;" } + %path{ d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" } + %line{ x1: "7", y1: "7", x2: "7.01", y2: "7" } + = link_to t('.tags'), project_tags_path(@project) + .tags-container + - tags = @project.tag_list.split(' ') + - if tags.any? + - tags.each do |tag| + = link_to tag, tags_path(names: tag), class: 'tag' + - else + %span.tag.tag-empty + = t('.no_tags') + - if @project.edit_authorized? + = link_to t('.add_tags'), project_tags_path(@project) + + - if @project.project_badges.active.any? + %section#project_badges + = show_badges + = more_badges_link + + / In a Nutshell + .gradient-card + .card-content + %h3= t('.in_a_nutshell', name: truncate_project_name(@project.name, 40)) + - if @analysis.present? + %ul.nutshell-list + %li + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %line{ x1: "22", y1: "2", x2: "11", y2: "13" } + %polygon{ points: "22 2 15 22 11 13 2 9 22 2" } + .content + has had + = link_to "#{number_with_delimiter @analysis.commit_count} commits", summary_project_commits_path(@project) + made by + = link_to "#{number_with_delimiter @analysis.committers_all_time} contributors", summary_project_contributors_path(@project) + + %li + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %polyline{ points: "16 18 22 12 16 6" } + %polyline{ points: "8 6 2 12 8 18" } + .content + representing + = link_to "#{number_with_delimiter @analysis.code_total} lines of code", languages_summary_project_analysis_path(@project, id: 'latest') + + - if @analysis.main_language + %li + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %path{ d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" } + %polyline{ points: "14 2 14 8 20 8" } + %line{ x1: "16", y1: "13", x2: "8", y2: "13" } + %line{ x1: "16", y1: "17", x2: "8", y2: "17" } + %polyline{ points: "10 9 9 9 8 9" } + .content + is + = link_to "mostly written in #{@analysis.main_language.nice_name}", languages_summary_project_analysis_path(@project, id: 'latest') + with average comment density + + %li + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %polyline{ points: "23 6 13.5 15.5 8.5 10.5 1 18" } + %polyline{ points: "17 6 23 6 23 12" } + .content + has a + %span.font-semibold mature codebase + maintained by a + %span.font-semibold very large development team + + %li + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %circle{ cx: "12", cy: "12", r: "10" } + %polyline{ points: "12 6 12 12 16 14" } + .content + took an estimated + = link_to "#{number_with_delimiter @analysis.man_years.ceil} years of effort", estimated_cost_project_path(@project) + (COCOMO model) + + %li + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %rect{ x: "3", y: "4", width: "18", height: "18", rx: "2", ry: "2" } + %line{ x1: "16", y1: "2", x2: "16", y2: "6" } + %line{ x1: "8", y1: "2", x2: "8", y2: "6" } + %line{ x1: "3", y1: "10", x2: "21", y2: "10" } + .content + - if @analysis.first_commit_time? + starting + %span.font-semibold= @analysis.first_commit_time.strftime('%B %Y') + - if @analysis.last_commit_time? + , ending + %span.font-semibold= "#{time_ago_in_words(@analysis.last_commit_time)} ago" + - else + = render partial: 'projects/show/no_analysis_summary' + + / Quick Reference + .gradient-card + .card-content + %h3= t('.quick_reference.quick_reference') + %ul.quick-ref-list + - if @project.organization + %li + %span.label= t('.organization') + %span.value + = link_to @project.organization.name, organization_path(@project.organization) + + %li + %span.label= link_to t('.quick_reference.proj_links'), project_links_path(@project) + %span.value + - if @project.decorate.sorted_link_list.any? + - @project.decorate.sorted_link_list.each do |link_category_name, links| + - if links.size > 1 + = link_to project_links_path(@project) do + = h(link_category_name) + %i= t('.quick_reference.no_of_links', count: links.size) + - elsif links.size == 1 + - link_opts = link_category_name == 'Homepage' ? { itemprop: 'url' } : {} + = link_to h(links.first.url), link_opts do + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", style: "width:12px;height:12px;vertical-align:middle;margin-right:2px;" } + %path{ d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" } + %polyline{ points: "15 3 21 3 21 9" } + %line{ x1: "10", y1: "14", x2: "21", y2: "3" } + = h(link_category_name) + %br + - else + = link_to t('.quick_reference.add_proj_links'), project_links_path(@project) + + %li + %span.label= link_to t('.quick_reference.code_locations'), project_enlistments_path(@project) + %span.value + - enlistments = @project.enlistments + - if enlistments.present? && ApiAccess.available? + - if enlistments.size > 1 + = link_to t('.quick_reference.n_locations', n: enlistments.size), project_enlistments_path(@project) + - else + = enlistments.first.code_location.url.truncate(35) + - else + = link_to t('.quick_reference.add_code_location'), project_enlistments_path(@project) + + %li + %span.label= link_to t('.quick_reference.similar_projects'), similar_project_path(@project) + %span.value + %div#similar_projects{ data: { project_id: @project.to_param } } + .hidden#related_spinner= image_tag('spinner.gif', alt: '') + + %li + %span.label= link_to t('.quick_reference.managers'), project_managers_path(@project) + %span.value + - if @project.active_managers.present? + != project_managers_list + - else + = link_to t('.quick_reference.become_first', what: @project.name), new_project_manager_path(@project) + + / Mobile: Collapsible accordion cards + .mobile-accordion + / Project Summary - Collapsible + .gradient-card.accordion-card + %button.accordion-header{ onclick: "toggleAccordion(this)" } + .header-left + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %circle{ cx: "12", cy: "12", r: "10" } + %line{ x1: "12", y1: "16", x2: "12", y2: "12" } + %line{ x1: "12", y1: "8", x2: "12.01", y2: "8" } + %h3= t('.project_summary') + %svg.chevron{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %polyline{ points: "6 9 12 15 18 9" } + .accordion-content{ style: "display: none;" } + - if @project.description.present? + %p= simple_format(@project.description.strip_tags, {}, wrapper_tag: 'span') - else - = t('.no_tags') - - if @project.edit_authorized? - = link_to t('.add_tags'), project_tags_path(@project) - - if @project.project_badges.active.present? - %section#project_badges - %h4.title= link_to t('project_badges.index.title'), project_project_badges_path(@project) - = show_badges - = more_badges_link - - .col-md-4.nutshell_container - .well - - p_name = truncate_project_name(@project.name, 40) - %h4.nutshell_heading!= t('.in_a_nutshell', name: @project.name.length > 40 ? p_name : p_name + '...') - - if @analysis.present? - = render 'projects/show/factoids' - - else - = render partial: 'projects/show/no_analysis_summary' - .col-md-4.quick_reference_container - .well - = render partial: 'projects/show/quick_reference' - -.row.mezzo -= render partial: 'projects/show/licenses' - -.row.mezzo -.project_security - = render partial: 'projects/show/security' - -.row.mezzo -- if @analysis.empty? - .analysis_blank_container - .row.project_row - .col-md-12.blank_message_container - .col-md-8.analysis_alert_container - = render partial: 'projects/show/no_analysis_summary' - .col-md-4#proj_rating= render 'projects/show/community_rating', title: t('.community_rating'), score: @score - - if @project.enlistments.empty? && @analysis.empty? - = image_tag('sample_ohloh_analysis.jpg', class: 'col-md-12') -- else - = render partial: 'projects/show/analysis_summary' - -- if @scan_analytics.present? - .row.mezzo - #scan_data_row - .project_row= render partial: 'projects/show/scan_analytics_summary', collection: @scan_analytics, as: :scan_analytics + %p + = t('.no_description') + = link_to t('.add_description'), edit_project_path(@project) + + %section#project_tags_mobile + %h4 + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", style: "width: 1rem; height: 1rem; display: inline-block; vertical-align: text-top;" } + %path{ d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" } + %line{ x1: "7", y1: "7", x2: "7.01", y2: "7" } + = link_to t('.tags'), project_tags_path(@project) + .tags-container + - tags = @project.tag_list.split(' ') + - if tags.any? + - tags.each do |tag| + = link_to tag, tags_path(names: tag), class: 'tag' + - else + %span.tag.tag-empty= t('.no_tags') + + / In a Nutshell - Collapsible + .gradient-card.accordion-card + %button.accordion-header{ onclick: "toggleAccordion(this)" } + .header-left + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %line{ x1: "12", y1: "20", x2: "12", y2: "10" } + %line{ x1: "18", y1: "20", x2: "18", y2: "4" } + %line{ x1: "6", y1: "20", x2: "6", y2: "16" } + %h3= t('.in_a_nutshell', name: truncate_project_name(@project.name, 40)) + %svg.chevron{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %polyline{ points: "6 9 12 15 18 9" } + .accordion-content{ style: "display: none;" } + - if @analysis.present? + %ul.nutshell-list + %li + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %line{ x1: "22", y1: "2", x2: "11", y2: "13" } + %polygon{ points: "22 2 15 22 11 13 2 9 22 2" } + .content + has had + = link_to "#{number_with_delimiter @analysis.commit_count} commits", summary_project_commits_path(@project) + made by + = link_to "#{number_with_delimiter @analysis.committers_all_time} contributors", summary_project_contributors_path(@project) + / Add remaining factoids as in desktop version + - else + = render partial: 'projects/show/no_analysis_summary' + + / Quick Reference - Collapsible + .gradient-card.accordion-card + %button.accordion-header{ onclick: "toggleAccordion(this)" } + .header-left + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %path{ d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" } + %polyline{ points: "15 3 21 3 21 9" } + %line{ x1: "10", y1: "14", x2: "21", y2: "3" } + %h3= t('.quick_reference.quick_reference') + %svg.chevron{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %polyline{ points: "6 9 12 15 18 9" } + .accordion-content{ style: "display: none;" } + %ul.quick-ref-list + / Same content as desktop version + - if @project.organization + %li + %span.label= t('.organization') + %span.value= link_to @project.organization.name, organization_path(@project.organization) + %li + %span.label= link_to t('.quick_reference.proj_links'), project_links_path(@project) + %span.value + - if @project.decorate.sorted_link_list.any? + - @project.decorate.sorted_link_list.each do |link_category_name, links| + - if links.size > 1 + = link_to project_links_path(@project) do + = h(link_category_name) + %i= t('.quick_reference.no_of_links', count: links.size) + - elsif links.size == 1 + - link_opts = link_category_name == 'Homepage' ? { itemprop: 'url' } : {} + = link_to h(links.first.url), link_opts do + = h(link_category_name) + %br + - else + = link_to t('.quick_reference.add_proj_links'), project_links_path(@project) + %li + %span.label= link_to t('.quick_reference.code_locations'), project_enlistments_path(@project) + %span.value + - enlistments = @project.enlistments + - if enlistments.present? && ApiAccess.available? + - if enlistments.size > 1 + = link_to t('.quick_reference.n_locations', n: enlistments.size), project_enlistments_path(@project) + - else + = enlistments.first.code_location.url.truncate(35) + - else + = link_to t('.quick_reference.add_code_location'), project_enlistments_path(@project) + %li + %span.label= link_to t('.quick_reference.similar_projects'), similar_project_path(@project) + %span.value + %div#similar_projects_mobile{ data: { project_id: @project.to_param } } + .hidden#related_spinner_mobile= image_tag('spinner.gif', alt: '') + %li + %span.label= link_to t('.quick_reference.managers'), project_managers_path(@project) + %span.value + - if @project.active_managers.present? + != project_managers_list + - else + = link_to t('.quick_reference.become_first', what: @project.name), new_project_manager_path(@project) + + / Licenses Section + = render partial: 'projects/show/licenses' + + / Project Security Section + = render partial: 'projects/show/security' + + / Code, Activity, Community Grid + - if @analysis.present? + = render partial: 'projects/show/analysis_summary' + +:javascript + function toggleAccordion(button) { + const content = button.nextElementSibling; + const chevron = button.querySelector('.chevron'); + const isExpanded = content.style.display === 'block'; + content.style.display = isExpanded ? 'none' : 'block'; + chevron.classList.toggle('expanded', !isExpanded); + } diff --git a/app/views/projects/show/_analysis_summary.html.haml b/app/views/projects/show/_analysis_summary.html.haml index 3f8ce4612..94e5be92d 100755 --- a/app/views/projects/show/_analysis_summary.html.haml +++ b/app/views/projects/show/_analysis_summary.html.haml @@ -1,76 +1,103 @@ -.row.project_row.row-eq-height - - if @project.enlistments.empty? - .alert.alert-info.alert-block.col-md-11{ style: 'margin-left: 15px;' } - %h3= bootstrap_icon('icon-info-sign', t('.code_locations_removed')) - .indent{ style: 'padding: 15px 0 0 15px;' } - %p= t('.code_locations_removed_explanation') - - .col-md-4.right_border.top_section{ itemscope: '', itemtype: 'http://schema.org/Language' } - %h2.center= t('.code') - = render partial: 'projects/show/lines_of_code_chart' - - .col-md-4.right_border.top_section - %h2.center= t('.activity') - .col-md-12.manage_padding.chart_container +/ Code, Activity, Community Grid +.analysis-grid + / Code Card + .gradient-card.analysis-card + .card-content + %h3.section-header= t('.code') + + %h4.chart-title + = link_to t('.lines_of_code'), languages_summary_project_analysis_path(@project, id: 'latest') + + - if @analysis.present? + - chart_url = lines_of_code_project_analysis_path(@project, id: 'latest') + .chart.watermark440#loc_chart{ datasrc: chart_url, style: 'width: 100%; height: 200px;' } + - else + %p.no-data= t('.no_code_locations') + + %h4.subsection-title= t('.languages') + = render partial: 'projects/show/languages' + + / Activity Card + .gradient-card.analysis-card + .card-content + %h3.section-header= t('.activity') + + .chart-header + %h4.chart-title + = link_to t('.commits_per_month'), summary_project_commits_path(@project) + + - if @analysis.present? - - chart_url = commits_history_project_analysis_url(project_id: @project.to_param, id: 'latest') - %h4= link_to t('.commits_per_month'), summary_project_commits_path(@project) - .col-md-12.manage_padding - #activity_chart.chart.watermark440{ 'datasrc' => chart_url, style: 'width: 100%; min-height: 210px' } + - chart_url = commits_history_project_analysis_path(@project, id: 'latest') + #activity_chart.chart.watermark440{ datasrc: chart_url, style: 'width: 100%; min-height: 210px' } - else - %h4= t('.commits_per_month') - - add_code_link = link_to t('.add_a_code_location'), new_project_enlistment_path(@project) - %p= t('.no_code_locations', link: add_code_link) - .clearfix - - .col-md-4.community_container.top_section - %h2.center= t('.community') - .col-md-12.manage_padding.chart_container - %div{ style: 'width: 100%;' }= render partial: 'projects/show/committers_per_month_chart' - .clearfix - .clearfix - -.row.project_row.row-eq-height - .col-md-4.right_border.bottom_section.languages_container - = render partial: 'projects/show/languages' - .col-md-4.right_border.bottom_section - .col-xs-12.resize_well - .well.activity_well - %table.unstyled#activity_table{ width: 'auto' } - %tbody - %tr - %td.col-xs-5.no_padding_left{ valign: 'top' } - %h4.section_header.thirty_day= t('.thirty_day_summary') - %small.summary_timespan.thirty_day - - if @analysis.present? && @analysis.thirty_day_summary && @analysis.oldest_code_set_time - = (@analysis.oldest_code_set_time - 30.days).strftime('%b %e %Y') - — - = @analysis.oldest_code_set_time.strftime('%b %e %Y') - - else - = t('.not_available') - .clearfix - = render partial: 'projects/show/thirty_day_summary' - %td.col-xs-5.col-xs-offset-1.no_padding_right{ valign: 'top' } - %h4.section_header.twelve_month= t('.twelve_month_summary') - %small.summary_timespan - - if @analysis.twelve_month_summary.present? && @analysis.oldest_code_set_time - = (@analysis.oldest_code_set_time - 12.months).strftime('%b %e %Y') - — - = @analysis.oldest_code_set_time.strftime('%b %e %Y') - - else - = t('.not_available') - .clearfix - = render partial: 'projects/show/twelve_month_summary' - .col-md-4.community_container.bottom_section - .col-xs-12.resize_well - .well.community_well - .row - .col-xs-12 - = render partial: 'projects/show/community_recent_committers' - .row.proj_community_ratings#proj_rating - = render partial: 'projects/show/community_rating', locals: { score: @score } - .row.manage_padding - .col-md-12   - .row.manage_padding - .col-md-6#stackoverflow_recent_questions - .col-md-6#stackoverflow_popular_questions + %p.no-data= t('.no_commits') + + .summary-grid + .summary-section.thirty-day-summary + - thirty_day_summary = @analysis&.thirty_day_summary + %h5.summary-header= t('.thirty_day_summary') + %p.summary-timespan + - if thirty_day_summary && @analysis.oldest_code_set_time + = (@analysis.oldest_code_set_time - 30.days).strftime('%b %e %Y') + — + = @analysis.oldest_code_set_time.strftime('%b %e %Y') + - else + = t('.not_available') + + %p.summary-stat + %span.stat-number= number_with_delimiter(thirty_day_summary&.commits_count.to_i) + = t('.commits') + %p.summary-stat + %span.stat-number= number_with_delimiter(thirty_day_summary&.committer_count.to_i) + = t('.contributors') + + .summary-section + %h5.summary-header= t('.twelve_month_summary') + %p.summary-timespan + - if @analysis.twelve_month_summary.present? && @analysis.oldest_code_set_time + = (@analysis.oldest_code_set_time - 12.months).strftime('%b %e %Y') + — + = @analysis.oldest_code_set_time.strftime('%b %e %Y') + - else + = t('.not_available') + + - if @analysis.twelve_month_summary.present? + - previous_summary = @analysis.previous_twelve_month_summary + %p.summary-stat + %span.stat-number= number_with_delimiter(@analysis.twelve_month_summary.commits_count) + Commits + - if previous_summary&.data? + - diff = previous_summary.commits_difference + %p.summary-change{ class: diff > 0 ? 'positive' : 'negative' } + = diff > 0 ? "Up +#{number_with_delimiter(diff.abs)}" : "Down -#{number_with_delimiter(diff.abs)}" + - prev_commits = previous_summary.commits_count + - if prev_commits.to_i > 0 + (#{(diff.abs.to_f / prev_commits.to_f * 100).floor}%) + from previous 12 + + %p.summary-stat + %span.stat-number= number_with_delimiter(@analysis.twelve_month_summary.committer_count) + Contributors + - if previous_summary&.data? + - diff = previous_summary.committers_difference + %p.summary-change{ class: diff > 0 ? 'positive' : 'negative' } + = diff > 0 ? "Up +#{number_with_delimiter(diff.abs)}" : "Down -#{number_with_delimiter(diff.abs)}" + - prev_committers = previous_summary.committer_count + - if prev_committers.to_i > 0 + (#{(diff.abs.to_f / prev_committers.to_f * 100).floor}%) + from previous 12 months + + / Community Card + .gradient-card.analysis-card + .card-content + %h3.section-header= t('.community') + + .community-chart-wrap + = render partial: 'projects/show/committers_per_month_chart' + + %h4.subsection-title= t('.most_recent_contributors') + = render partial: 'projects/show/community_recent_committers' + + .ratings-section + = render partial: 'projects/show/community_rating', locals: { score: @score } diff --git a/app/views/projects/show/_committers_per_month_chart.html.haml b/app/views/projects/show/_committers_per_month_chart.html.haml index 8c626e94f..12258833f 100644 --- a/app/views/projects/show/_committers_per_month_chart.html.haml +++ b/app/views/projects/show/_committers_per_month_chart.html.haml @@ -1,9 +1,9 @@ - if @analysis - committers_url = committer_history_project_analysis_url(project_id: @project.to_param, id: 'latest') - %h4= link_to t('.contributors_per_month'), summary_project_contributors_path(@project) + %h4.chart-title= link_to t('.contributors_per_month'), summary_project_contributors_path(@project) .col-md-12.manage_padding #community_chart.chart.watermark440{ 'datasrc' => committers_url, style: 'width: 100%; height: 200px' } - else - %h4= t('.contributors_per_month') + %h4.chart-title= t('.contributors_per_month') - add_code_link = link_to t('.add_a_code_location'), new_project_enlistment_path(@project) %p= t('.no_code_locations', link: add_code_link) diff --git a/app/views/projects/show/_community_rating.html.haml b/app/views/projects/show/_community_rating.html.haml index debe63b18..077a50dab 100644 --- a/app/views/projects/show/_community_rating.html.haml +++ b/app/views/projects/show/_community_rating.html.haml @@ -1,40 +1,56 @@ -- title = @project.best_analysis.present? ? t('.page_title_1') : t('.page_title_2') +- title = @project.best_analysis.present? ? t('.page_title_1') : t('.page_title_2') +- avg = @project.rating_average.to_f +- user_score = score.to_i +- logged_in = current_user.present? -.clear -.col-xs-12.no_padding#community_rating - .col-xs-6 - %h4.community_rating_header= link_to title, summary_project_reviews_path(@project) - - if @project.ratings.any? - .col-md-12 - = t((@project.ratings.count == 1 ? '.one_user_rate' : '.users_rate'), n: @project.ratings.count) - .clear - .col-md-12 - .pull-left - != rating_stars('average_rating_stars', @project.rating_average.to_f || 0) - .rating_stars_value - #{number_with_precision(@project.rating_average || 0, precision: 1)}/5.0 - - else - %span{ style: 'padding-left: 8px; display: block;' }= t('.be_the_first') - .col-xs-6.add_rating_container - .col-xs-12.no_padding - - if @rating - = t('.click_to_edit') +.rating-content + .rating-top-row + .rating-main + %h4.community_rating_header= link_to title, summary_project_reviews_path(@project) + - if @project.ratings.any? + %p.rating-info + = link_to t((@project.ratings.count == 1 ? '.one_user_rate' : '.users_rate'), n: @project.ratings.count), summary_project_reviews_path(@project), class: 'rating-link' + + .rating-display + .stars-display + - (1..5).each do |i| + - if i <= avg.floor + %svg.star-icon.star-filled{ viewBox: '0 0 24 24', fill: '#ffb91a', stroke: '#ffb91a', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', width: '14', height: '14', xmlns: 'http://www.w3.org/2000/svg' } + %polygon{ points: '12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2' } + - elsif i == avg.ceil && (avg % 1) >= 0.3 + %svg.star-icon.star-half{ viewBox: '0 0 24 24', fill: '#ffb91a', stroke: '#ffb91a', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', width: '14', height: '14', xmlns: 'http://www.w3.org/2000/svg', style: 'opacity: 0.5' } + %polygon{ points: '12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2' } + - else + %svg.star-icon.star-empty{ viewBox: '0 0 24 24', fill: 'none', stroke: '#d1d5db', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', width: '14', height: '14', xmlns: 'http://www.w3.org/2000/svg' } + %polygon{ points: '12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2' } + %span.rating-value + = "#{number_with_precision(@project.rating_average || 0, precision: 1)} /5.0" - else - = t('.click_to_add') - .clear - .jrating.pull-left{ id: @project.to_param, class: needs_login_or_verification_or_default, score: score, - star_style: 'big', style: 'margin-left: 9px; margin-bottom: 5px;', - data: { show: 'projects/show/community_rating' } } - %span.pull-left#rating_spinner -    - = image_tag('spinner.gif') + %p.rating-info= t('.be_the_first') + .rating-action + %p.rating-info + - if @rating + = t('.click_to_edit') + - else + = t('.click_to_add') + .interactive-stars{ 'data-rate-url' => rate_project_path(@project), + 'data-unrate-url' => unrate_project_path(@project), + 'data-user-score' => user_score, + 'data-logged-in' => logged_in.to_s, + 'data-show' => 'projects/show/community_rating' } + - (1..5).each do |i| + - filled = i <= user_score + %button.star-btn{ type: 'button', 'data-rating' => i, title: "Rate #{i} out of 5", 'aria-label': "Rate #{i} out of 5" } + %svg.star-svg{ viewBox: '0 0 24 24', fill: filled ? '#ffb91a' : 'none', stroke: filled ? '#ffb91a' : '#d1d5db', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', width: '16', height: '16', xmlns: 'http://www.w3.org/2000/svg' } + %polygon{ points: '12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2' } - if @rating - %span.pull-left.clearfix#clear{ style: 'padding-left: 10px;' } - = link_to t('.clear'), 'javascript:void(0);', class: 'btn btn-mini', - 'data-url' => unrate_project_path(@project, - show: 'projects/show/community_rating'), - method: :delete - - unless current_user && Review.by_account(current_user).for_project(@project).count > 0 - %span{ style: 'margin-left: 5px;' } - = link_to t('.review_this_project'), new_project_review_path(@project) + %a.clear-rating-btn.rating-link{ href: 'javascript:void(0);', + 'data-url' => unrate_project_path(@project, show: 'projects/show/community_rating') } + = t('.clear') + - unless current_user && Review.by_account(current_user).for_project(@project).count > 0 + %p.rating-info.rating-secondary + = link_to new_project_review_path(@project), class: 'rating-link' do + %svg.rating-action-icon{ viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', width: '12', height: '12' } + %polygon{ points: '12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2' } + %span= t('.review_this_project') diff --git a/app/views/projects/show/_community_recent_committers.html.haml b/app/views/projects/show/_community_recent_committers.html.haml index b0d174d24..46aea669e 100644 --- a/app/views/projects/show/_community_recent_committers.html.haml +++ b/app/views/projects/show/_community_recent_committers.html.haml @@ -1,21 +1,21 @@ - if @analysis && @analysis.all_time_summary - %h4= link_to t('.most_recent'), project_contributors_path(@project, sort: 'latest_commit') - recent_contributors = @analysis.all_time_summary.recent_contribution_persons - if recent_contributors.any? - %table#recent_committers_table - - recent_contributors.take(6).in_groups_of(2) do |persons| - %tr{ height: '40px;' } - - persons.compact.each do |p| - %td{ width: '1%' }= avatar_img_for(p, 24) - :ruby - contrib_id = - if p.account_id - Contribution.generate_id_from_project_id_and_account_id(@project.id, p.account_id) - else - p.id - end - %td.recent_committers{ width: '49%', title: obfuscate_email(p.person_name) } - - obfuscated_email = obfuscate_email(p.person_name).reverse.truncate(17).reverse - = link_to h(obfuscated_email), project_contributor_path(@project.id, contrib_id) + .contributors-grid + - recent_contributors.take(8).each do |p| + :ruby + contrib_id = if p.account_id + Contribution.generate_id_from_project_id_and_account_id(@project.id, p.account_id) + else + p.id + end + person_name = obfuscate_email(p.person_name) + truncated_name = person_name.reverse.truncate(20).reverse + + .contributor-item + = image_tag avatar_img_path(p, 24), class: 'contributor-avatar', alt: person_name + = link_to truncated_name, project_contributor_path(@project.id, contrib_id), class: 'contributor-name', title: person_name - else - %p= t('.no_one_recently') + %p.no-data= t('.no_one_recently') +- else + %p.no-data= t('.no_contributors') diff --git a/app/views/projects/show/_cwe.html.haml b/app/views/projects/show/_cwe.html.haml index 94ac06102..e1474ded4 100644 --- a/app/views/projects/show/_cwe.html.haml +++ b/app/views/projects/show/_cwe.html.haml @@ -2,7 +2,7 @@ - if cwes.blank? %p No top 25 CWE defects were found. - else - %table.table.table-bordered.table-hover.well + %table.table.table-bordered.table-hover.oh-card.oh-card--flush %thead %tr %th.text-right ID diff --git a/app/views/projects/show/_header.html.haml b/app/views/projects/show/_header.html.haml index c1958de21..384cd6c37 100644 --- a/app/views/projects/show/_header.html.haml +++ b/app/views/projects/show/_header.html.haml @@ -1,57 +1,89 @@ -#project_masthead - .col-md-1.no_padding#project_icon - %a{ href: project_path(@project), itemprop: 'url' } - = @project.decorate.icon(:med) - - .col-md-11#project_header - .pull-left.project_title - %h1.float_left{ itemprop: 'name' } - = link_to h(@project.name), project_path(@project), style: 'color: black', itemprop: 'url' - %small.pull-left.clear_both - != link_to bootstrap_icon('icon-cogs', t('projects.header.settings')), settings_project_path(@project) - - if @project.is_a_duplicate - - if current_user_is_admin? || (@current_account && @project.is_a_duplicate.account == @current_account) - | - != link_to bootstrap_icon('icon-flag', t('projects.header.edit_duplicate')), - edit_project_duplicate_path(@project, @project.is_a_duplicate) - - else - | - = link_to bootstrap_icon('icon-flag', t('projects.header.duplicate')), new_project_duplicate_path(@project) - - if current_user_is_admin? - | - = link_to bootstrap_icon('icon-legal', t('projects.header.job')), oh_admin_project_jobs_path(@project) - - if current_user.present? && @project.analysis_updated_or_project_created_time < 1.week.ago - | - = link_to bootstrap_icon('icon-lock', t('projects.header.outdated')), report_outdated_project_path(@project), method: :put - .pull-right.no_padding#widgets - .pull-left - #add_this{ 'data-analytics-id' => ENV['ADDTHIS_ANALYTICS_ID'] } - %p - .addthis_custom_sharing.addthis - %script{ src: '//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-500da8c658f6dda7' } - .pull-right{ itemtype: 'CreativeWork', itemprop: 'interactionCount' } - #i_use_this_container - .use_count - = link_to number_with_delimiter(@project.user_count), users_project_path(@project) - - unless @project.deleted? - - if logged_in? - = render partial: 'projects/show/i_use_this', locals: { project: @project } +.project-header-gradient + .header-content + .header-main + .project-title-section + %h1{ itemprop: 'name' } + = link_to @project.name, project_path(@project) + + .action-buttons + = link_to settings_project_path(@project), class: 'action-btn action-btn-outline' do + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", 'stroke-linecap': "round", 'stroke-linejoin': "round" } + %circle{ cx: "12", cy: "12", r: "3" } + %path{ d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" } + = t('.settings') + + - if @project.is_a_duplicate + - if current_user_is_admin? || (@current_account && @project.is_a_duplicate.account == @current_account) + = link_to edit_project_duplicate_path(@project, @project.is_a_duplicate), class: 'action-btn action-btn-outline' do + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", 'stroke-linecap': "round", 'stroke-linejoin': "round" } + %path{ d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" } + %polyline{ points: "14 2 14 8 20 8" } + %line{ x1: "16", y1: "13", x2: "8", y2: "13" } + %line{ x1: "16", y1: "17", x2: "8", y2: "17" } + %polyline{ points: "10 9 9 9 8 9" } + = t('.edit_duplicate') + - else + = link_to new_project_duplicate_path(@project), class: 'action-btn action-btn-outline' do + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", 'stroke-linecap': "round", 'stroke-linejoin': "round" } + %path{ d: "M4 15s1-1 4-1 5 2 9 2V3s-4 0-6-2-9-1-3 1-4 1-4 1z" } + %line{ x1: "4", y1: "22", x2: "4", y2: "15" } + = t('.report_duplicate') + + - if current_user_is_admin? + = link_to oh_admin_project_jobs_path(@project), class: 'action-btn action-btn-outline', target: '_blank', rel: 'noopener noreferrer' do + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", 'stroke-linecap': "round", 'stroke-linejoin': "round" } + %rect{ x: "3", y: "7", width: "18", height: "13", rx: "2", ry: "2" } + %path{ d: "M16 3v4M8 3v4M3 11h18" } + = t('projects.header.job') + + - if current_user.present? && @project.analysis_updated_or_project_created_time < 1.week.ago + = link_to report_outdated_project_path(@project), method: :put, class: 'action-btn action-btn-outline' do + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", 'stroke-linecap': "round", 'stroke-linejoin': "round" } + %circle{ cx: "12", cy: "12", r: "10" } + %polyline{ points: "12 6 12 12 16 14" } + = t('projects.header.outdated') + + .project-description{ itemprop: 'description' } + - if @project.description.present? + = truncate(@project.description.strip_tags, length: 200) - else - .btn.btn-mini.i_use_this_btn{ 'data-target' => '#LoginModal', 'data-toggle' => 'modal' }= t('.i_use_this') - .modal.fade#LoginModal{ 'aria-hidden' => 'true', 'aria-labelledby' => 'LoginModalLabel', :role => 'dialog' } - .modal-dialog.modal-sm - .modal-content{ style: 'width: 398px' } - .modal-header - %button.close{ 'aria-label' => 'Close', 'data-dismiss' => 'modal', :type => 'button' } - %span{ 'aria-hidden' => 'true' } × - %h4.modal-title#LoginModalLabel= t('.login_required') - .model-body - = render 'sessions/sign_in' - #project_header_activity_indicator - - project_activity_level_class(@project, :thirtyfive) - - project_activity_level_text(@project, :thirtyfive) - -.clearfix -.row.mezzo -= yield :header_title if content_for?(:header_title) -= render partial: 'layouts/partials/alert' + = t('.no_description') + + = project_analysis_timestamp(@project) + + / Stats and button (shown below on mobile, top-right on desktop) + .header-top-right + .activity-indicator-wrapper + / Activity indicator will be rendered here + - project_activity_level_class(@project, :thirtyfive) + - project_activity_level_text(@project, :thirtyfive) + + .stats-card + .stats-inner + / Users icon + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %path{ d: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" } + %circle{ cx: "9", cy: "7", r: "4" } + %path{ d: "M23 21v-2a4 4 0 0 0-3-3.87" } + %path{ d: "M16 3.13a4 4 0 0 1 0 7.75" } + .user-count + = link_to number_with_delimiter(@project.user_count), users_project_path(@project) + + - unless @project.deleted? + - if logged_in? + = render partial: 'projects/show/i_use_this', locals: { project: @project } + - else + %button.i-use-this-btn.not-using{ 'data-toggle': "modal", 'data-target': "#LoginModal" } + = t('.i_use_this') + +/ Login Modal for non-logged in users +- unless logged_in? + .modal.fade#LoginModal{ 'aria-hidden': 'true', 'aria-labelledby': 'LoginModalLabel', role: 'dialog' } + .modal-dialog.modal-sm + .modal-content + .modal-header + %button.close{ 'aria-label': 'Close', 'data-dismiss': 'modal', type: 'button' } + %span{ 'aria-hidden': 'true' } × + %h4.modal-title#LoginModalLabel= t('.login_required') + .model-body + = render 'sessions/sign_in' diff --git a/app/views/projects/show/_i_use_this.html.haml b/app/views/projects/show/_i_use_this.html.haml index 962fe8b02..4276c8faf 100644 --- a/app/views/projects/show/_i_use_this.html.haml +++ b/app/views/projects/show/_i_use_this.html.haml @@ -1,2 +1,5 @@ -%a.btn.btn-mini.new-stack-entry.i_use_this_btn{ href: 'javascript:', 'data-project-id' => project.to_param } +%button.i-use-this-btn.new-stack-entry.i_use_this_btn{ href: 'javascript:', 'data-project-id' => project.to_param } + %svg{ xmlns: 'http://www.w3.org/2000/svg', width: '16', height: '16', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', style: 'vertical-align: middle; margin-right: 4px;' } + %path{ d: 'M22 11.08V12a10 10 0 1 1-5.93-9.14' } + %polyline{ points: '22 4 12 14.1 9 11.1' } = t('.i_use_this') diff --git a/app/views/projects/show/_languages.html.haml b/app/views/projects/show/_languages.html.haml index 954c0c522..094cf5397 100644 --- a/app/views/projects/show/_languages.html.haml +++ b/app/views/projects/show/_languages.html.haml @@ -1,26 +1,23 @@ -:ruby - languages_image_options = { project_id: @project.to_param, id: 'latest', height: 75, width: 75 } +- if @analysis && !@analysis.empty? + :ruby + languages_image_options = { project_id: @project.to_param, id: 'latest', height: 90, width: 90 } + language_data = Analysis::LanguagePercentages.new(@analysis).collection -.col-md-12.manage_padding.language_table_container - - if @analysis - - if @analysis.empty? - = t('.no_source') - - else - %h4= link_to t('.languages'), languages_summary_project_analysis_path(@project, id: 'latest') - .pull-left.language_pie_image{ style: 'padding-right: 0.5rem;' } - = image_tag languages_project_analysis_url(languages_image_options) + - if language_data.any? + .languages-content + .language-pie-container + = image_tag languages_project_analysis_url(languages_image_options), class: 'language-pie-chart', alt: t('.pie_chart_alt', project: @project.name) - %table.table.table-striped.unstyled.pull-left.language_table - - Analysis::LanguagePercentages.new(@analysis).collection.each do |id, name, attr| + .language-legend + - language_data.take(5).each do |id, name, attr| - percent = attr[:percent] > 0 ? attr[:percent] : '<1' - %tr.pull-left.language_stat_box - - color = attr[:color] - %td.language_legends - .pull-left{ style: "width:5px;height:12px;margin-top:5px;margin-right:10px;background-color:##{color}" } - = id ? link_to(name, language_path(id), itemprop: 'name') : name - %td.language_percentage_indicator - %span{ itemscope: '', itemprop: 'aggregateRating', itemtype: 'http://schema.org/AggregateRating' } - %span{ itemprop: 'ratingValue' } #{percent}% + - color = attr[:color] + .language-item + .language-color-box{ style: "background-color: ##{color}" } + %span.language-name + = id ? link_to(name, language_path(id)) : name + %span.language-percent= "#{percent}%" - else - %h4= t('.languages') - %p!= t('.no_code_locations', link: link_to(t('.add_a_code_location'), new_project_enlistment_path(@project))) + %p.no-data= t('.no_source') +- else + %p.no-data= t('.no_source') diff --git a/app/views/projects/show/_license_details.html.haml b/app/views/projects/show/_license_details.html.haml index 8c9beb0a2..15c69a92f 100644 --- a/app/views/projects/show/_license_details.html.haml +++ b/app/views/projects/show/_license_details.html.haml @@ -2,7 +2,7 @@ %h5.license_title= link_to h(license.name), license_url(license.to_param) .row .col-md-4#permitted - .well.right_border + .oh-card.right_border %h5 Permitted - permissions = license.permitted_license_permissions - permissions.each do |p| @@ -10,7 +10,7 @@ %span{ 'data-tipso' => p.description }= p.name %i.fa.float_right{ 'aria-hidden' => 'true', class: p.icon, 'data-tipso' => p.name } .col-md-4#forbidden - .well.right_border + .oh-card.right_border %h5 Forbidden - permissions = license.forbidden_license_permissions - permissions.each do |p| @@ -18,7 +18,7 @@ %span{ 'data-tipso' => p.description }= p.name %i.fa.float_right{ 'aria-hidden' => 'true', class: p.icon, 'data-tipso' => p.name } .col-md-4#required - .well + .oh-card %h5 Required - permissions = license.required_license_permissions - permissions.each do |p| diff --git a/app/views/projects/show/_licenses.html.haml b/app/views/projects/show/_licenses.html.haml index be69fbda4..e0d1ea141 100644 --- a/app/views/projects/show/_licenses.html.haml +++ b/app/views/projects/show/_licenses.html.haml @@ -1,16 +1,85 @@ - if @project.licenses.count > 0 - .row.mezzo - .project_licenses - .row.project_row - .col-md-12.license_title_container - %h4.license_panel_title - = link_to t('.title'), project_licenses_path(@project) - .clearfix - .col-md-2 - .col-md-8#license_details - .well= render partial: 'projects/show/license_details', collection: @project.licenses, as: :license - .col-md-2 - %div.all-license-div - %h4.float-right - = link_to t('.all'), licenses_path, target: '_blank', class: 'license_link' + .licenses-section + %h2.section-title= link_to t('.title'), project_licenses_path(@project) + - @project.licenses.each do |license| + .license-card.gradient-card + %button.license-header{ onclick: "toggleLicense(this)", class: "license-toggle-#{license.id}", type: "button" } + .header-left + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", 'stroke-linecap': "round", 'stroke-linejoin': "round" } + %path{ d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" } + %h4= link_to (license.name), license_url(license.to_param), style: 'text-decoration: none;' + + %svg.chevron{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", 'stroke-linecap': "round", 'stroke-linejoin': "round" } + %polyline{ points: "6 9 12 15 18 9" } + + .license-content{ class: "license-content-#{license.id}" } + .license-details-grid + / Permitted Column + .license-detail-card.permitted + %h5 + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", 'stroke-linecap': "round", 'stroke-linejoin': "round" } + %circle{ cx: "12", cy: "12", r: "10" } + %path{ d: "m9 12 2 2 4-4" } + = t('.permitted') + %ul.margin_left_25 + - license.permitted_license_permissions.each do |p| + %p + %span{ 'data-tipso' => p.description }= p.name + %i.fa.float_right{ 'aria-hidden' => 'true', class: "fa-#{p.icon}", 'data-tipso' => p.name } + + / Forbidden Column + .license-detail-card.forbidden + %h5 + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", 'stroke-linecap': "round", 'stroke-linejoin': "round" } + %circle{ cx: "12", cy: "12", r: "10" } + %line{ x1: "15", y1: "9", x2: "9", y2: "15" } + %line{ x1: "9", y1: "9", x2: "15", y2: "15" } + = t('.forbidden') + %ul.margin_left_25 + - license.forbidden_license_permissions.each do |p| + %p + %span{ 'data-tipso' => p.description }= p.name + %i.fa.float_right{ 'aria-hidden' => 'true', class: "fa-#{p.icon}", 'data-tipso' => p.name } + + / Required Column + .license-detail-card.required + %h5 + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2", 'stroke-linecap': "round", 'stroke-linejoin': "round" } + %circle{ cx: "12", cy: "12", r: "10" } + %line{ x1: "12", y1: "16", x2: "12", y2: "12" } + %path{ d: "M12 8h.01" } + = t('.required') + %ul.margin_left_25 + - license.required_license_permissions.each do |p| + %p + %span{ 'data-tipso' => p.description }= p.name + %i.fa.float_right{ 'aria-hidden' => 'true', class: "fa-#{p.icon}", 'data-tipso' => p.name } + + .license-disclaimer + %p= t('.disclaimer') + + .view-all-link + = link_to "#{t('.view_all_licenses')} →", licenses_path, target: '_blank' + +:javascript + function toggleLicense(button) { + // Only toggle on mobile (screen width < 1024px) + if (window.innerWidth >= 1024) { + return; + } + + const content = button.nextElementSibling; + const chevron = button.querySelector('.chevron'); + const isExpanded = content.classList.contains('expanded'); + + if (isExpanded) { + content.classList.remove('expanded'); + content.style.display = 'none'; + chevron.classList.remove('expanded'); + } else { + content.classList.add('expanded'); + content.style.display = 'block'; + chevron.classList.add('expanded'); + } + } diff --git a/app/views/projects/show/_no_analysis_summary.html.haml b/app/views/projects/show/_no_analysis_summary.html.haml index b3f8a5e6c..396940b13 100644 --- a/app/views/projects/show/_no_analysis_summary.html.haml +++ b/app/views/projects/show/_no_analysis_summary.html.haml @@ -7,17 +7,20 @@ message = t('.message_2') if no_code_available message = t('.message_3') if analysis_not_complete .no_analysis_message - .alert.alert-info.alert-block{ style: 'padding-left: 30px;' } - .title - %h3!= bootstrap_icon('icon-info-sign', message) - .indent{ style: 'margin-top: 15px;' } + .modern-info-card + .info-card-header + .info-icon-wrapper + %i.icon-info-sign + .info-content + %h3.info-title= message + .info-card-body - if no_recognized_code - %p= t('.help_1') + %p.info-text= t('.help_1') - if no_code_available %p= t('.help_2') %p= t('.help_3') %p{ align: 'center' } = link_to t('.add_a_code_location'), new_project_enlistment_path(@project), class: 'btn btn-large btn-primary' - if analysis_not_complete - - link = link_to(t('.check_its_progress'), project_enlistments_path(@project)) - %span.analysis_in_progress!= t('.how_it_computes', link: link) + - link = link_to(t('.check_its_progress'), project_enlistments_path(@project), class: 'info-link') + %p.info-text.analysis_in_progress!= t('.how_it_computes', link: link) diff --git a/app/views/projects/show/_quick_reference.html.haml b/app/views/projects/show/_quick_reference.html.haml index 19daa5e36..2ec5db7ca 100644 --- a/app/views/projects/show/_quick_reference.html.haml +++ b/app/views/projects/show/_quick_reference.html.haml @@ -40,7 +40,7 @@ = link_to t('.similar_projects'), similar_project_path(@project) .col-xs-7{ style: 'margin-bottom: .5em;' } #similar_projects{ data: { project_id: @project.to_param } } - .hidden#related_spinner= image_tag('spinner.gif') + .hidden#related_spinner= image_tag('spinner.gif', alt: '') .clearfix .col-xs-5.text-right = link_to t('.managers'), project_managers_path(@project) diff --git a/app/views/projects/show/_security.html.haml b/app/views/projects/show/_security.html.haml index 0391ab395..ea15f7392 100644 --- a/app/views/projects/show/_security.html.haml +++ b/app/views/projects/show/_security.html.haml @@ -1,18 +1,84 @@ - blog_url = 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Project-Security' -.row.project_row - - if @project.best_project_security_set.try(:most_recent_vulnerabilities?) || @project.project_vulnerability_report - .col-md-12.security_title_container - %h4.security-panel-title= t('.title') - .clearfix - .row-eq-height - .col-md-4#vulnerability_per_version - = render partial: 'projects/show/vulnerabilities_per_version' - .col-md-4#vulnerability-report - .well= render partial: 'projects/show/vulnerability_report' - .col-md-4#did_you_know - = render 'projects/show/site_features' - = link_to t('.blog_link'), blog_url, class: 'security_blog_link', target: '_blank' - - else - .col-md-12#did_you_know - %h4= t('.title_nvd') - = render 'projects/show/site_features' +- if @project.best_project_security_set.try(:most_recent_vulnerabilities?) || @project.project_vulnerability_report + #security.security-section + %h2.section-title= t('.title', default: 'Project Security') + + .security-grid + / Vulnerabilities per Version Card + .gradient-card + .card-content + %h4.card-subtitle + = link_to t('.vulnerabilities_per_version.title', default: 'Vulnerabilities per Version'), security_project_path(@project), class: 'security-link' + %span.subtitle-detail= t('.vulnerabilities_per_version.title_detail', default: '(Most recent versions)') + - if @project.best_project_security_set.try(:most_recent_vulnerabilities?) + - chart_url = recent_vulnerabilities_version_chart_project_path(@project) + #vulnerability_version_chart.chart-container{ data: { src: chart_url } } + - else + %p.no-data= t('vulnerabilities.index.no_vulnerability') + + / Vulnerability Report Card + .gradient-card + .card-content + %h4.card-subtitle + = link_to t('.vulnerability_report.title', default: 'Vulnerability Report'), security_project_path(@project), class: 'security-link' + + - pvr = @project.project_vulnerability_report + - if pvr + - pvr_link = 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-About-the-Project-Vulnerability-Report' + + .vulnerability-indices + / Security Confidence Index + .index-section + .index-title= t('.vulnerability_report.pss.title', default: 'Security Confidence Index') + .gradient-bar-container + .gradient-bar + .indicator-pointer{ style: "left: #{100 - pointer_from_right(pvr).to_f}%", 'data-tipso': pss_content(pvr), 'data-tipso-title': t('.vulnerability_report.pss.title') } + .index-labels + %span.label-left= t('.vulnerability_report.pss.poor') + %span.label-right= t('.vulnerability_report.pss.good') + + / Vulnerability Exposure Index + .index-section + .index-title= t('.vulnerability_report.pvs.title', default: 'Vulnerability Exposure Index') + .gradient-bar-container + .gradient-bar + .indicator-pointer{ style: "left: #{100 - pointer_from_right(pvr, pvs: true).to_f}%", 'data-tipso': pvs_content(pvr), 'data-tipso-title': t('.vulnerability_report.pvs.title') } + .index-labels + %span.label-left= t('.vulnerability_report.pvs.poor') + %span.label-right= t('.vulnerability_report.pvs.good') + + .report-link-container + = link_to t('.vulnerability_report.blog_link', default: 'About Project Vulnerability Report'), pvr_link, class: 'report-link', target: '_blank' + - else + %p.no-data= t('.vulnerability_report.no_data') + + / Did You Know Card + .did-you-know-card + .card-content + %h4.did-you-know-title + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %circle{ cx: "12", cy: "12", r: "10" } + %path{ d: "M12 16v-4" } + %path{ d: "M12 8h.01" } + = t('.site_features.did_you_know', default: 'Did You Know...') + %ul.feature-list + - random_site_features.each do |feature| + %li= feature.html_safe + + .security-footer-link + = link_to "#{t('.blog_link', default: 'About Project Security')} →", blog_url, target: '_blank' + +- else + #security.security-section + %h2.section-title= t('.title_nvd', default: 'This Project has No vulnerabilities Reported Against it') + .did-you-know-card.single + .card-content + %h4.did-you-know-title + %svg{ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", 'stroke-width': "2" } + %circle{ cx: "12", cy: "12", r: "10" } + %path{ d: "M12 16v-4" } + %path{ d: "M12 8h.01" } + = t('.site_features.did_you_know', default: 'Did You Know...') + %ul.feature-list + - random_site_features.each do |feature| + %li= feature.html_safe diff --git a/app/views/projects/show/_similar_by_tags.html.haml b/app/views/projects/show/_similar_by_tags.html.haml index 71663bea9..dcb7532e4 100644 --- a/app/views/projects/show/_similar_by_tags.html.haml +++ b/app/views/projects/show/_similar_by_tags.html.haml @@ -1,15 +1,10 @@ - if similar_by_tags.any? - %table - - similar_by_tags.take(4).in_groups_of(2, false) do |projects| - %tr - - projects.compact.each do |p| - %td{ valign: 'top' } - %a.pull-left{ href: project_path(p) } - - project_icon(p, :small, width: 24) - %a.pull-left.proj-name{ href: project_path(p) }= truncate(p.name, length: 15) + .similar-projects-list + - similar_by_tags.take(4).each do |p| + = link_to truncate(p.name, length: 20), project_path(p), class: 'similar-proj-name' - elsif @project.tags.empty? - if @project.edit_authorized? - %a{ href: project_tags_path(@project) }= t('.add_some_tags') + = link_to t('.add_some_tags'), project_tags_path(@project) - else = t('.none') - else diff --git a/app/views/projects/show/_site_features.html.haml b/app/views/projects/show/_site_features.html.haml index 71b53dab4..c343ab352 100644 --- a/app/views/projects/show/_site_features.html.haml +++ b/app/views/projects/show/_site_features.html.haml @@ -1,4 +1,4 @@ -.well +.oh-card %h4= t('.did_you_know') + '...' %ul.unstyled - random_site_features.each do |feature| diff --git a/app/views/projects/similar.html.haml b/app/views/projects/similar.html.haml index 35e2bc68e..77d87c45a 100644 --- a/app/views/projects/similar.html.haml +++ b/app/views/projects/similar.html.haml @@ -1,36 +1,35 @@ - content_for(:html_title) { t('.page_title', name: @project.name) } - page_context[:select_footer_nav] = :similar_projects -.project_content_title - %h2.pull-left= t('.title') - = project_analysis_timestamp(@project) +.similar-page + .similar-page-header + %h2.similar-page-title= t('.title') -%div - %h3 - = pluralize_without_count(@similar_by_tags.length, t('.project')) - = t('.similar_to') - = @project.name + .similar-section + %h3.similar-section-heading + = pluralize_without_count(@similar_by_tags.length, t('.project')) + = t('.similar_to') + %span.similar-project-name= @project.name - - @similar_by_tags.in_groups_of(3, false) do |columns| - .col-md-12 - - columns.each do |project| - = render partial: 'similar_project', locals: { project: project } - -- if @similar_by_tags.empty? - %p= t('.nothing_by_tags') - %p - = link_to t('.add_tags'), project_tags_path(@project) - = t('.to_similar') + - if @similar_by_tags.any? + .similar-grid + - @similar_by_tags.each do |project| + = render partial: 'similar_project', locals: { project: project } + - else + .similar-empty-state + %p.similar-empty-text= t('.nothing_by_tags') + %p.similar-empty-text + = link_to t('.add_tags'), project_tags_path(@project), class: 'similar-action-link' + = t('.to_similar') -.clearfix - .margin_top_30 - %h3= t('.also_use', name: @project.name) + .similar-section + %h3.similar-section-heading= t('.also_use', name: @project.name) - - @similar_by_stacks.in_groups_of(3, false) do |columns| - .col-md-12 - - columns.each do |project| + - if @similar_by_stacks.any? + .similar-grid + - @similar_by_stacks.each do |project| = render partial: 'similar_project', locals: { project: project } - - - if @similar_by_stacks.empty? - %p= t('.nothing_by_stacks') - %p= t('.nightly_update') + - else + .similar-empty-state + %p.similar-empty-text= t('.nothing_by_stacks') + %p.similar-empty-text.similar-muted= t('.nightly_update') diff --git a/app/views/projects/users.html.haml b/app/views/projects/users.html.haml index 7711ec13e..95879b1f6 100644 --- a/app/views/projects/users.html.haml +++ b/app/views/projects/users.html.haml @@ -3,31 +3,31 @@ page_context[:select_top_menu_nav] = 'select_projects' page_context[:select_footer_nav] = :users -%div - %h2.pull-left= t('.users') - = project_analysis_timestamp(@project) +.pu-page + .pu-page__title + .pu-page__title-icon + %i.fa.fa-users + %h2 + %strong= t('.users') -.clearfix + - if params[:query].nil? && @accounts.none? + = render 'shared/alert', message: t('.no_users') + - else + = render partial: 'shared/search_dingus', locals: { collection: @accounts, sort_context: :project_users } -- if params[:query].nil? && @accounts.none? - = render 'shared/alert', message: t('.no_users') -- else - = render partial: 'shared/search_dingus', locals: { collection: @accounts, sort_context: :project_users } + .pu-user-list + - @accounts.each do |account| + .pu-user-card + .pu-user-card__left + = link_to avatar_img_for(account.person), avatar_path(account.person), class: 'pu-user-card__avatar-link' + .pu-user-card__info + %h3.pu-user-card__name= link_to h(account.person.person_name), avatar_path(account.person) + .pu-user-card__stacks!= stack_name(account) + .pu-user-card__contributions + %p.pu-user-card__contributions-label= t('.contributions') + = render partial: '/projects/person_contribution', locals: { person: account.person } + .pu-user-card__right + %p.pu-user-card__kudo-label= t('.kudo_rank') + = avatar_small_laurels(account.person.kudo_rank) -- @accounts.each do |account| - .row.margin_bottom_25 - .col-md-12 - .col-md-3 - .avatar - = link_to avatar_img_for(account.person), avatar_path(account.person) - .avatar_name - %h4.nomargin= link_to h(account.person.person_name), avatar_path(account.person) - .margin_left_50.small!= stack_name(account) - .col-md-6 - %h6.soft.margin_top_0= t('.contributions') - = render partial: '/projects/person_contribution', locals: { person: account.person } - .col-md-2.kudo_rank.pull-right - %h6.soft.margin_top_0= t('.kudo_rank') - = avatar_small_laurels(account.person.kudo_rank) - -= will_paginate @accounts + = render 'shared/modern_pagination', collection: @accounts diff --git a/app/views/reviews/_add_review.html.haml b/app/views/reviews/_add_review.html.haml index ab6cce305..53bef21a4 100644 --- a/app/views/reviews/_add_review.html.haml +++ b/app/views/reviews/_add_review.html.haml @@ -1,19 +1,17 @@ -%table{ width: '100%', boder: '1' } - %tr - %td{ colspan: '3' } - %h3= t('reviews.my_review', name: truncate(@project.name, length: 40)) - %tr - %td{ width: '64%' } - - if @rating.try(:persisted?) - .pull-left - = t('.not_reviewd').html_safe - - else - = t('.not_reviewd_or_rated').html_safe - %td{ width: '15%' } - %strong= t('reviews.my_rating') - %td{ width: '21%' } - != rating_stars('rating_star', @rating.try(:score).to_i) - %tr - %td - .clear{ Style: 'margin-top:5px;' } - = icon_button(new_project_review_url(@project), text: 'New Review', size: 'small', type: 'primary') +.oh-review-card + .oh-review-card__body + .rr-add-review + .rr-add-review__icon + %i.fa.fa-pencil + .rr-add-review__content + %p.rr-add-review__message + - if @rating.try(:persisted?) + = t('.not_reviewd').html_safe + - else + = t('.not_reviewd_or_rated').html_safe + .rr-add-review__meta + - if @rating.try(:score) + %strong= t('reviews.my_rating') + != rating_stars('rating_star', @rating.score.to_i) + .oh-review-card__footer + = icon_button(new_project_review_url(@project), text: t('reviews.new_review'), size: 'small', type: 'primary') diff --git a/app/views/reviews/_buttons.html.haml b/app/views/reviews/_buttons.html.haml index 45ef020a6..057a375bd 100644 --- a/app/views/reviews/_buttons.html.haml +++ b/app/views/reviews/_buttons.html.haml @@ -1,15 +1,11 @@ -.no_margin_left{ style: 'margin-top:10px;' } - - if logged_in? - .pull-left - - if current_user_is_admin? || review.account.id == current_user.id - = icon_button(edit_project_review_path(review.project, review), icon: 'pencil', text: t('reviews.edit_review'), - size: 'mini no-border', type: 'info') - = icon_button(review_path(review), icon: 'trash', text: t('reviews.remove_review'), size: 'mini no-border', - type: 'danger', method: :delete, data: { confirm: t('reviews.remove_confirmation') }, - style: 'margin-left: 7px') -   - - if current_user_is_admin? - = icon_button(make_spammer_account_accesses_path(review.account), text: t('reviews.spam'), method: :post, - data: { confirm: t('reviews.spam_confirmation', name: review.account.name) }, size: 'mini no-border', - type: 'warning', title: t('reviews.spam_title', name: review.account.name)) - .clearfix +- if logged_in? + - if current_user_is_admin? || review.account.id == current_user.id + = icon_button(edit_project_review_path(review.project, review), icon: 'pencil', + text: t('reviews.edit_review'), size: 'mini', type: 'info') + = icon_button(review_path(review), icon: 'trash', text: t('reviews.remove_review'), + size: 'mini', type: 'danger', method: :delete, + data: { confirm: t('reviews.remove_confirmation') }) + - if current_user_is_admin? + = icon_button(make_spammer_account_accesses_path(review.account), text: t('reviews.spam'), + method: :post, data: { confirm: t('reviews.spam_confirmation', name: review.account.name) }, + size: 'mini', type: 'warning', title: t('reviews.spam_title', name: review.account.name)) diff --git a/app/views/reviews/_community_and_your_rating.haml b/app/views/reviews/_community_and_your_rating.haml index bb42b769f..d38ab84db 100644 --- a/app/views/reviews/_community_and_your_rating.haml +++ b/app/views/reviews/_community_and_your_rating.haml @@ -1,9 +1,18 @@ - project ||= @project -.clearfix.margin_top_5 - %h6.soft= t('.community_rating') - .nomargin.clearfix - .col-md-12 - != rating_stars('rating_star', project.rating_average.to_f) - %p.soft.small#average_rating_details_2 - = t('.rating_status', avg_rating: "#{number_with_precision(project.rating_average || 0, precision: 1)}/5.0", - ratings_count: project.ratings.count, reviews_count: project.reviews.count).html_safe + +.rr-community-card + %h3.rr-community-card__title= t('.community_rating') + .rr-community-card__grid + .rr-community-card__stat + .rr-community-card__stars + != rating_stars('rating_star', project.rating_average.to_f) + %p.rr-community-card__label Overall + .rr-community-card__stat + .rr-community-card__value= "#{number_with_precision(project.rating_average || 0, precision: 1)}/5.0" + %p.rr-community-card__label Average Rating + .rr-community-card__stat + .rr-community-card__value= project.ratings.count + %p.rr-community-card__label Number of Ratings + .rr-community-card__stat + .rr-community-card__value= project.reviews.count + %p.rr-community-card__label Number of Reviews diff --git a/app/views/reviews/_existing_review.html.haml b/app/views/reviews/_existing_review.html.haml index 6088b0287..5135029c6 100644 --- a/app/views/reviews/_existing_review.html.haml +++ b/app/views/reviews/_existing_review.html.haml @@ -1,27 +1,32 @@ .review_container{ id: "existing_review_#{existing_review.id}" } - %table{ width: '100%', boder: '1' } - %tr - %td{ colspan: '3' } - %h3= t('reviews.my_review', name: truncate(@project.name, length: 40)) - %tr - %td{ width: '65%', valign: 'top' } - %label.control-label - = existing_review.title - %abbr.soft.small{ title: existing_review.created_at } - = t('reviews.written_ago', time: time_ago_in_words(existing_review.created_at)) - %td{ width: '14%', valign: 'top' } - %label.control-label= t('reviews.my_rating') - %td{ width: '21%', valign: 'top' } - != rating_stars('rating_star', existing_review.score.to_f) - %tr - %td{ colspan: '3' } - %p.description.review_content= simple_format(existing_review.comment.fix_encoding_if_invalid) - %tr - %td - = icon_button(edit_project_review_path(existing_review.project, existing_review), icon: 'pencil', - text: t('reviews.edit_review'), size: 'mini', type: 'info') - - if current_user_is_admin? || existing_review.account == current_user - = icon_button(review_path(existing_review), icon: 'trash', text: t('reviews.remove_review'), size: 'mini', - type: 'danger', method: :delete, data: { confirm: t('reviews.remove_confirmation') }, - style: 'margin-left: 5px') -   + %h3.oh-review-section-title + %i.fa.fa-pencil + = t('reviews.my_review', name: truncate(@project.name, length: 40)) + + .oh-review-card + .oh-review-card__body + .oh-review-author-row + .oh-review-avatar + %span= existing_review.account&.login&.first&.upcase || 'U' + .oh-review-meta + .oh-review-meta__header + .oh-review-meta__left + %p.oh-review-author + = existing_review.account&.login + %span.oh-review-author__says= ' says:' + .oh-review-title-row + %label.oh-review-title= existing_review.title + != rating_stars('rating_star', existing_review.score.to_f) + .oh-review-meta__right + != rating_stars('rating_star', existing_review.score.to_f) + .oh-review-timestamp + %i.fa.fa-clock-o + = t('reviews.written_ago', time: time_ago_in_words(existing_review.created_at)) + .oh-review-comment= simple_format(existing_review.comment.fix_encoding_if_invalid) + + .oh-review-card__footer + = icon_button(edit_project_review_path(existing_review.project, existing_review), icon: 'pencil', + text: t('reviews.edit_review'), size: 'medium', type: 'info') + - if current_user_is_admin? || existing_review.account == current_user + = icon_button(review_path(existing_review), icon: 'trash', text: t('reviews.remove_review'), size: 'medium', + type: 'danger', method: :delete, data: { confirm: t('reviews.remove_confirmation') }) diff --git a/app/views/reviews/_fields.html.haml b/app/views/reviews/_fields.html.haml index 908215326..5d53f5b79 100644 --- a/app/views/reviews/_fields.html.haml +++ b/app/views/reviews/_fields.html.haml @@ -4,16 +4,17 @@ = render partial: 'rater', locals: { score: @rating.try(:score) || 0 } .col-md-12 .col-md.margin_top_15 - %b.required= t('.headline') - .margin_top_5 - = f.text_field :title, class: 'col-md-7' + %label.control-label.required{ for: 'review_title' }= t('.headline') + .controls + = f.text_field :title, class: 'form-control col-md-7', id: 'review_title' - error_tag @review, :title .clearfix + .control-group.margin_top_5 - %b.required= t('.review') - .margin_top_5 + %label.control-label.required{ for: 'review_comment' }= t('.review') + .controls = preserve do - = f.text_area :comment, max_length: 5000, size: '45x5', class: 'col-md-11' + = f.text_area :comment, max_length: 5000, size: '45x5', class: 'form-control col-md-11', id: 'review_comment' - error_tag @review, :comment .clearfix diff --git a/app/views/reviews/_new_or_existing_review.html.haml b/app/views/reviews/_new_or_existing_review.html.haml index 94a5d7d15..2c4d4e36a 100644 --- a/app/views/reviews/_new_or_existing_review.html.haml +++ b/app/views/reviews/_new_or_existing_review.html.haml @@ -1,4 +1,4 @@ -.well{ style: 'padding-top:5px;' } +.rr-my-review-section - if @account_reviews.present? = render partial: 'existing_review', collection: @account_reviews - else diff --git a/app/views/reviews/_rater.html.haml b/app/views/reviews/_rater.html.haml index cffbfdf57..fe4a726b0 100644 --- a/app/views/reviews/_rater.html.haml +++ b/app/views/reviews/_rater.html.haml @@ -4,7 +4,7 @@ .controls.pull-left{ style: 'margin-left:10px;' } .jrating{ id: @project.to_param, score: score, star_style: 'big', data: { show: 'reviews/rater' } } .margin_left_10.pull-left#rating_spinner - = image_tag('spinner.gif') + = image_tag('spinner.gif', alt: '') - if @rating.try(:persisted?) %span#clear{ style: 'margin-left:7px;' } = link_to t('.clear'), 'javascript:void(0);', class: 'btn btn-mini', diff --git a/app/views/reviews/_review_list.html.haml b/app/views/reviews/_review_list.html.haml index 64d90ab08..003f62c66 100644 --- a/app/views/reviews/_review_list.html.haml +++ b/app/views/reviews/_review_list.html.haml @@ -1,40 +1,48 @@ -.well.review_container{ id: "review_list_#{review_list.id}" } - .row - .col-md-2 +.oh-review-card.review_container{ id: "review_list_#{review_list.id}" } + .oh-review-card__body + .oh-review-author-row - if @project - account = review_list.account - .padding_right_0 - .pull-right - = link_to h(account.name.reverse.truncate(11).reverse), account_url(account), title: account.name - says: - .avatar_margin_left.pull-right.margin_right_10 - = avatar_for(review_list.account, size: 58) + .oh-review-avatar + %span= account.name.first.upcase + .oh-review-meta + .oh-review-meta__header + .oh-review-meta__left + %p.oh-review-author + = link_to h(truncate(account.name, length: 20)), account_url(account), title: account.name + %span.oh-review-author__says says: + .oh-review-title-row + - if review_list.title + %label.oh-review-title= truncate(review_list.title.camelize, length: 35) + != rating_stars('rating_star', review_list.score.to_f, mini: true) + .oh-review-timestamp + %i.fa.fa-clock-o + %abbr{ title: review_list.created_at } + = t('reviews.written_ago', time: time_ago_in_words(review_list.created_at)) + .oh-review-comment= simple_format(review_list.comment.fix_encoding_if_invalid) + .oh-review-helpful + = render partial: 'helpful_count_status', locals: { review: review_list } + = render partial: 'helpful_yes_or_no_links', locals: { review: review_list } - elsif @account - project = review_list.project - .margin_left_20 - - project_icon(project, :med) - .clearfix - .margin_left_20 - = link_to h(truncate(project.name, length: 20)), project_path(project), title: project.name - - .col-md-10.padding_left_30 - %strong.pull-left - - if review_list.title - = truncate(review_list.title.camelize, length: 38) - - else - = t('.accounts_review', name: review_list.account.name) -   - .pull-left{ style: 'margin-top:1px' } - != rating_stars('rating_star', review_list.score.to_f, mini: true) - .clear - .col-md - %abbr.soft.small{ title: review_list.created_at } - = t('reviews.written_ago', time: time_ago_in_words(review_list.created_at)) - %p= simple_format(review_list.comment.fix_encoding_if_invalid) - .soft.small - .pull-left - = render partial: 'helpful_count_status', locals: { review: review_list } - .pull-right{ style: 'padding-right: 120px' } - = render partial: 'helpful_yes_or_no_links', locals: { review: review_list } - .clearfix - = render partial: 'buttons', locals: { review: review_list } + .oh-review-avatar + %span= project.name.first.upcase + .oh-review-meta + .oh-review-meta__header + .oh-review-meta__left + %p.oh-review-author + = link_to h(truncate(project.name, length: 20)), project_path(project), title: project.name + .oh-review-title-row + - if review_list.title + %label.oh-review-title= truncate(review_list.title.camelize, length: 35) + != rating_stars('rating_star', review_list.score.to_f, mini: true) + .oh-review-timestamp + %i.fa.fa-clock-o + %abbr{ title: review_list.created_at } + = t('reviews.written_ago', time: time_ago_in_words(review_list.created_at)) + .oh-review-comment= simple_format(review_list.comment.fix_encoding_if_invalid) + .oh-review-helpful + = render partial: 'helpful_count_status', locals: { review: review_list } + = render partial: 'helpful_yes_or_no_links', locals: { review: review_list } + .oh-review-card__footer + = render partial: 'buttons', locals: { review: review_list } diff --git a/app/views/reviews/_review_summary.html.haml b/app/views/reviews/_review_summary.html.haml index 77e849b57..faab0dab7 100644 --- a/app/views/reviews/_review_summary.html.haml +++ b/app/views/reviews/_review_summary.html.haml @@ -1,23 +1,26 @@ -.well.review_container{ id: "review_summary_#{review_summary.id}" } +.oh-review-card.review_container{ id: "review_summary_#{review_summary.id}" } - account = review_summary.account - .row - .col-md-3.no_padding - .pull-right - = link_to h(truncate(account.name, length: 11)), account_url(account), title: account.name - says: - .pull-right.padding_right_15 - = avatar_for(account, size: 58) - .col-md-9.no_padding_right.padding_left_25 - %strong.pull-left - = (review_summary.title ? truncate(review_summary.title.camelize, length: 35) : "#{account.name}'s Review") -   - .pull-left{ style: 'margin-top:1px' } - != rating_stars('rating_star', review_summary.score.to_f, mini: true) - .clearfix - .col-md - %abbr.soft.small{ title: review_summary.created_at } - = t('reviews.written_ago', time: time_ago_in_words(review_summary.created_at)) - %p= simple_format(review_summary.comment.fix_encoding_if_invalid) - %p= render partial: 'helpful_count_status', locals: { review: review_summary } - %p= render partial: 'helpful_yes_or_no_links', locals: { review: review_summary } - = render partial: 'buttons', locals: { review: review_summary } + .oh-review-card__body + .oh-review-author-row + .oh-review-avatar + %span= account.name.first.upcase + .oh-review-meta + .oh-review-meta__header + .oh-review-meta__left + %p.oh-review-author + = link_to h(truncate(account.name, length: 20)), account_url(account), title: account.name + %span.oh-review-author__says says: + .oh-review-title-row + - if review_summary.title + %label.oh-review-title= truncate(review_summary.title.camelize, length: 35) + != rating_stars('rating_star', review_summary.score.to_f, mini: true) + .oh-review-timestamp + %i.fa.fa-clock-o + %abbr{ title: review_summary.created_at } + = t('reviews.written_ago', time: time_ago_in_words(review_summary.created_at)) + .oh-review-comment= simple_format(review_summary.comment.fix_encoding_if_invalid) + .oh-review-helpful + = render partial: 'helpful_count_status', locals: { review: review_summary } + = render partial: 'helpful_yes_or_no_links', locals: { review: review_summary } + .oh-review-card__footer + = render partial: 'buttons', locals: { review: review_summary } diff --git a/app/views/reviews/edit.html.haml b/app/views/reviews/edit.html.haml index 471320222..acd159b68 100644 --- a/app/views/reviews/edit.html.haml +++ b/app/views/reviews/edit.html.haml @@ -2,11 +2,10 @@ %h2.pull-left = link_to t('reviews.rating_and_reviews'), summary_project_reviews_path(@project) = t('edit') -= project_analysis_timestamp(@project) .clearfix -.col-md-9.col-md-offset-1 - .well +.col-md-9 + .oh-card -# form_for(@review, url: project_review_path(@project, @review)) do |f| = form_for([@project, @review]) do |f| = render partial: 'fields', locals: { f: f } diff --git a/app/views/reviews/index.html.haml b/app/views/reviews/index.html.haml index 978257687..12fd35192 100644 --- a/app/views/reviews/index.html.haml +++ b/app/views/reviews/index.html.haml @@ -3,14 +3,15 @@ - content_for(:html_title) { t('.title') } %h2 = link_to t('reviews.rating_and_reviews'), summary_project_reviews_path(@project) - = t('.listing') + %strong= t('.listing') - else - content_for(:html_title) { t('.account_title', name: @account.name) } - %h2= t('reviews.reviews_and_ratings') + %h2 + %strong= t('reviews.reviews_and_ratings') - if @reviews.present? = render partial: 'shared/search_dingus', locals: { collection: @reviews, sort_context: :reviews, no_match_found_type: :flash } = render partial: 'review_list', collection: @reviews - = will_paginate @reviews + = render 'shared/modern_pagination', collection: @reviews - else %h3= t('.no_reviews_yet') diff --git a/app/views/reviews/new.html.haml b/app/views/reviews/new.html.haml index 0a41d5440..18fd96df6 100644 --- a/app/views/reviews/new.html.haml +++ b/app/views/reviews/new.html.haml @@ -1,11 +1,10 @@ - content_for(:html_title) { t('.title') } %h2.pull_left = link_to t('reviews.rating_and_reviews'), summary_project_reviews_path(@project) - = t('new') -= project_analysis_timestamp(@project) + %strong= t('new') .clearfix %br -.col-md-9.no_padding{ style: 'margin-left:137px;' } - .well +.col-md-9 + .oh-card = form_for([@project, @review]) do |f| = render partial: 'fields', locals: { f: f } diff --git a/app/views/reviews/summary.html.haml b/app/views/reviews/summary.html.haml index a721eaf85..d8a288e38 100644 --- a/app/views/reviews/summary.html.haml +++ b/app/views/reviews/summary.html.haml @@ -1,22 +1,32 @@ - content_for(:html_title) { t('.title', name: @project.name) } -.project_content_title{ style: 'height: 50px' } - %h2.pull-left= t('.rating_and_reviews') - = project_analysis_timestamp(@project) -.row - .col.col-md-3.padding_left_25 - = render partial: 'community_and_your_rating' - .col.col-md-8.pull-right - = render partial: 'new_or_existing_review' -.clearfix -- if @project.reviews.exists? - .col-md-6.no_padding_left.padding_right_10 - %h3= link_to t('.most_helpful_reviews'), project_reviews_path(@project, sort: 'helpful') - = render partial: 'review_summary', collection: @most_helpful_reviews - .col-md-6.no_padding_right.padding_left_10 - %h3= link_to t('.most_recent_reviews'), project_reviews_path(@project, sort: 'recently_added') - = render partial: 'review_summary', collection: @recent_reviews - .clear - - if @project.reviews.count > 1 - = icon_button(project_reviews_path(@project), text: t('.see_all_reviews'), size: 'small', type: :primary) -- else - %p= t('.no_review') + +.rr-page + .rr-page__title + .rr-page__title-icon + %i.fa.fa-star + %h2 + %strong= t('.rating_and_reviews') + + = render partial: 'community_and_your_rating' + + = render partial: 'new_or_existing_review' + + - if @project.reviews.exists? + .rr-reviews-grid + .rr-reviews-grid__col + %h3.rr-section-title + .rr-section-title__icon.rr-section-title__icon--green + %i.fa.fa-thumbs-up + = link_to t('.most_helpful_reviews'), project_reviews_path(@project, sort: 'helpful') + = render partial: 'review_summary', collection: @most_helpful_reviews + .rr-reviews-grid__col + %h3.rr-section-title + .rr-section-title__icon + %i.fa.fa-clock-o + = link_to t('.most_recent_reviews'), project_reviews_path(@project, sort: 'recently_added') + = render partial: 'review_summary', collection: @recent_reviews + - if @project.reviews.count > 1 + .rr-see-all + = icon_button(project_reviews_path(@project), text: t('.see_all_reviews'), size: 'small', type: :primary) + - else + %p.rr-no-reviews= t('.no_review') diff --git a/app/views/rss_articles/index.html.haml b/app/views/rss_articles/index.html.haml index 586eb5367..f41212f32 100644 --- a/app/views/rss_articles/index.html.haml +++ b/app/views/rss_articles/index.html.haml @@ -2,7 +2,6 @@ - page_context[:select_footer_nav] = :rss %div %h2.pull-left= t('.title') - = render 'shared/analysis_timestamp', analysis: @project.best_analysis, project: @project .clearfix .col-md-12 @@ -34,4 +33,4 @@ - if @project %p= link_to(t('.edit'), project_rss_subscriptions_path(@project), class: "#{needs_login} btn btn-info") -= will_paginate @rss_articles += render 'shared/modern_pagination', collection: @rss_articles diff --git a/app/views/rss_feeds/_about_news_feeds.html.haml b/app/views/rss_feeds/_about_news_feeds.html.haml index 5e4848707..4c79020da 100644 --- a/app/views/rss_feeds/_about_news_feeds.html.haml +++ b/app/views/rss_feeds/_about_news_feeds.html.haml @@ -1,11 +1,17 @@ -%h4.margin_top_20 - %span.soft= t('.about_1') - = t('.about_2') -.col-md-9 - %p= t('.about_txt') - %p= t('.example') - .col-md-9 - %ul.margin_left_25 - %li= t('.url_1') - %li= t('.url_2') - %li= t('.url_3') +.about-account-basics-card + .card-header + %h4.card-title + %span.soft= t('.about_1') + = t('.about_2') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + .card-content + .about-content-wrapper + .col-md-9 + %p= t('.about_txt') + %p= t('.example') + .col-md-9 + %ul.margin_left_25 + %li= t('.url_1') + %li= t('.url_2') + %li= t('.url_3') diff --git a/app/views/rss_subscriptions/_fields.html.haml b/app/views/rss_subscriptions/_fields.html.haml index 17c801acf..f6c6e4eae 100644 --- a/app/views/rss_subscriptions/_fields.html.haml +++ b/app/views/rss_subscriptions/_fields.html.haml @@ -1,9 +1,9 @@ - has_permission = logged_in? && @project.edit_authorized? -%fieldset +%fieldset.rss-form .control-group - %label.control-label.required= t('.url') + %label.control-label.required{ for: 'rss_feed_url' }= t('.url') .controls - = f.text_field :url, class: 'col-md-11' + = f.text_field :url, class: 'form-control' - error_tag @rss_feed, :url %p= t('.examples') %ul.margin_left_25 diff --git a/app/views/rss_subscriptions/index.html.haml b/app/views/rss_subscriptions/index.html.haml index c6b6f12c2..072376f5d 100644 --- a/app/views/rss_subscriptions/index.html.haml +++ b/app/views/rss_subscriptions/index.html.haml @@ -7,39 +7,58 @@ = link_to t('.settings'), settings_project_path(@project) \: = t('.news_feeds') - = render 'shared/analysis_timestamp', analysis: @project.best_analysis, project: @project .clearfix - if @rss_subscriptions.empty? && !params[:query].present? = render partial: 'shared/alert', locals: { message: t('.no_feed_exists') } - else - %table.table-striped.table - %thead - %tr{ id: 'dingus-row' } - %td{ colspan: '3' } - = render partial: 'shared/search_dingus', locals: { collection: @rss_subscriptions, - sort_context: nil, no_match_found_type: :none } - %tr - %th= t('.news_feed') - %th= t('.update_status') - %th   - %tbody - - @rss_subscriptions.each do |rss_subscription| + = render partial: 'shared/search_dingus', locals: { collection: @rss_subscriptions, + sort_context: nil, no_match_found_type: :none } + .org-data-table.desktop-only + %table.table.table-striped.table-condensed + %thead %tr - %td.col-md-7 - = link_to bootstrap_icon('icon-external-link', rss_subscription.rss_feed.url), - rss_subscription.rss_feed.url, - target: '_blank' - %td.col-md-3= last_fetch_detail(rss_subscription) - %td.col-md-2.last - - if has_permission - = link_to bootstrap_icon('icon-trash', t('.remove')), - project_rss_subscription_path(@project, rss_subscription), - method: :delete, data: { confirm: t('.confirm_text') }, - class: 'btn btn-danger btn-lg pull-right' - - else - = disabled_button bootstrap_icon('icon-trash', t('.remove')), class: 'btn btn-danger pull-right' - = will_paginate @rss_subscriptions + %th= t('.news_feed') + %th= t('.update_status') + %th{ scope: 'col', aria: { label: t('.actions') } } + %tbody + - @rss_subscriptions.each do |rss_subscription| + %tr + %td.col-md-7 + = link_to bootstrap_icon('icon-external-link', rss_subscription.rss_feed.url), + rss_subscription.rss_feed.url, + target: '_blank' + %td.col-md-3= last_fetch_detail(rss_subscription) + %td.col-md-2.last + - if has_permission + = link_to project_rss_subscription_path(@project, rss_subscription), + method: :delete, data: { confirm: t('.confirm_text') }, + class: 'btn btn-sm btn-danger pull-right' do + %i.icon-trash + = t('.remove') + - else + = disabled_button bootstrap_icon('icon-trash', t('.remove')), class: 'btn btn-sm btn-danger pull-right' + + .rss-cards-container.mobile-only + - @rss_subscriptions.each do |rss_subscription| + .rss-card-item + .rss-card-url + = link_to bootstrap_icon('icon-external-link', rss_subscription.rss_feed.url), + rss_subscription.rss_feed.url, + target: '_blank' + .rss-card-meta + %span.rss-card-label= t('.update_status') + %span.rss-card-value= last_fetch_detail(rss_subscription) + .rss-card-actions + - if has_permission + = link_to project_rss_subscription_path(@project, rss_subscription), + method: :delete, data: { confirm: t('.confirm_text') }, + class: 'btn btn-sm btn-danger' do + %i.icon-trash + = t('.remove') + - else + = disabled_button bootstrap_icon('icon-trash', t('.remove')), class: 'btn btn-sm btn-danger' + = render 'shared/modern_pagination', collection: @rss_subscriptions - if @rss_subscriptions.empty? && params[:query].present? = render partial: 'shared/alert', locals: { message: t('.no_match') } diff --git a/app/views/rss_subscriptions/new.html.haml b/app/views/rss_subscriptions/new.html.haml index 5a2198ce8..53988cece 100644 --- a/app/views/rss_subscriptions/new.html.haml +++ b/app/views/rss_subscriptions/new.html.haml @@ -1,21 +1,26 @@ - content_for(:html_title) { t('.page_title', name: @project.name) } - page_context[:select_footer_nav] = :rss -%h2.pull-left - = link_to t('.settings'), settings_project_path(@project) +%h2 + %strong= link_to t('.settings'), settings_project_path(@project) \: - = link_to t('.news_feed'), project_rss_subscriptions_path(@project) - = t('.new') -= render 'shared/analysis_timestamp', analysis: @project.best_analysis, project: @project + %strong= link_to t('.news_feed'), project_rss_subscriptions_path(@project) + %strong= t('.new') .clearfix -.col-md-8.col-md-offset-2 - .well - = form_for @rss_feed, url: project_rss_subscriptions_url(@project), html: { method: :post } do |f| - = render partial: 'fields', locals: { f: f } +.col-md-10.oh-card + = form_for @rss_feed, url: project_rss_subscriptions_url(@project), html: { method: :post } do |f| + = render partial: 'fields', locals: { f: f } .clearfix -.coll-md-11 - %h4.soft= t('.about') - %ul.margin_left_25 - %li= t('.about_1') - %li= t('.about_2') +.about-account-basics-card + .card-header + %h4.card-title + %span.soft= t('.about') + %button.expand-toggle{ type: 'button', 'aria-label' => 'Expand about section' } + %i.fa.fa-chevron-down + .card-content + .about-content-wrapper + .col-md-9 + %ul.margin_left_25 + %li= t('.about_1') + %li= t('.about_2') diff --git a/app/views/scan_analytics/_cwe.html.haml b/app/views/scan_analytics/_cwe.html.haml index 0702e17ed..6b2f8714f 100644 --- a/app/views/scan_analytics/_cwe.html.haml +++ b/app/views/scan_analytics/_cwe.html.haml @@ -2,7 +2,7 @@ - if cwes.blank? %p= t('.cwe_no_data') - else - %table.table.table-bordered.table-hover.well + %table.table.table-bordered.table-hover.oh-card.oh-card--flush %thead %tr %th.text-right= t('.table_id') diff --git a/app/views/scan_analytics/_index.html.haml b/app/views/scan_analytics/_index.html.haml index 106f8d729..f6a504380 100644 --- a/app/views/scan_analytics/_index.html.haml +++ b/app/views/scan_analytics/_index.html.haml @@ -12,7 +12,7 @@ data: { remote: true, url: project_scan_analytics_path(@project), method: 'get' } %h5.title 1 of #{@analytics.size} enlistments .overlay-loader - %img.loader{ src: image_path('ui/loadingAnimation.gif') } + %img.loader{ src: image_path('ui/loadingAnimation.gif'), alt: '' } .row.row-eq-height.project_row#scan_display %h5#scan_analytic.text-left Coverity Scan Analysis (view project here) .row.project_row diff --git a/app/views/session_projects/_menu.html.haml b/app/views/session_projects/_menu.html.haml index a4255a8b1..d142d7d70 100644 --- a/app/views/session_projects/_menu.html.haml +++ b/app/views/session_projects/_menu.html.haml @@ -1,20 +1,43 @@ - if @session_projects.any? - .inner - %p.col.pull-left{ style: 'margin-right: 3em' } - %strong= t('.compare_projects') - - if @session_projects.size == 3 - %span.limit - = surround '(', ')' do - = t('.limit_reached') - %ul.nav.pull-left.margin_top_0 - - @session_projects.each_with_index do |project, i| - %li{ class: ('last' if i == @session_projects.length - 1) } - = form_tag nil, class: 'sp_form form-inline', id: "sp_frm_#{project.to_param}" do - %span.sp_label{ title: project.name } - = project.name.truncate(20) - %i.sp_input.icon.icon-remove-sign{ project_id: project.to_param } - %form.sp_form.form_inline.pull-right{ action: compare_projects_path, method: :get } - - @session_projects.each_with_index do |p, i| - %input{ type: 'hidden', name: "project_#{i}", value: p.name } - %input.btn.btn-sm#compare-submit-button{ type: 'submit', value: t('compare') } - .clearfix + / Toggle Button + .compare-toggle-tab + %button#compare_toggle_btn.compare-toggle-button + %i.fa.fa-exchange + = t('.compare_projects', count: @session_projects.size) + %i.fa.fa-chevron-up.toggle-icon + + / Expandable Tray Content + .compare-tray-content + .compare-tray-inner + / Selected Projects Grid + .compare-projects-grid + - @session_projects.each do |project| + .compare-project-slot + %button.remove-project{ type: 'button', data: { project_id: project.to_param } } + %i.fa.fa-times + .project-slot-icon + - if project.logo.present? + = image_tag project.logo.attachment.url(:small), alt: project.name + - else + %i.fa.fa-cube + .project-slot-info + %p.project-slot-name{ title: project.name }= project.name.truncate(25) + - if project.best_analysis + %p.project-slot-meta= t('.lines', count: project.best_analysis.code_total.to_human) + %p.project-slot-meta= t('.contributors', count: project.active_committers) + + - (3 - @session_projects.size).times do + .compare-project-slot.empty-slot + %i.fa.fa-plus + %span= t('.select_project') + + / Compare Action Button + .compare-action + %form{ action: compare_projects_path, method: :get } + - @session_projects.each_with_index do |p, i| + %input{ type: 'hidden', name: "project_#{i}", value: p.name } + %button.btn-compare-submit{ type: 'submit', disabled: @session_projects.size < 2 } + - if @session_projects.size >= 2 + = t('.compare_n_projects', count: @session_projects.size) + - else + = t('.compare_select_at_least') diff --git a/app/views/sessions/_sign_in.html.haml b/app/views/sessions/_sign_in.html.haml index 448f798d7..3ecc9217b 100644 --- a/app/views/sessions/_sign_in.html.haml +++ b/app/views/sessions/_sign_in.html.haml @@ -1,22 +1,26 @@ - @login = OpenStruct.new -= form_for(@login, as: :login, url: oh_sessions_path, html: { method: :post, class: 'well form-horizontal' }) do |f| += form_for(@login, as: :login, url: oh_sessions_path, html: { method: :post, id: 'signin_form' }) do |f| %fieldset - %legend= t('.log_in_to_open_hub') - .control-group - .input-prepend - %span.add-on - %i.icon-user - = f.text_field(:login, class: 'input-xlarge', placeholder: t('.login_or_email')) - .control-group.input-prepend - %span.add-on - %i.icon-key - = f.password_field(:password, class: 'input-xlarge', placeholder: t('.password')) - .control-group - %label.checkbox{ style: 'padding-left: -1px;' } + .signup-field-group + = f.label(:login, t('.login_or_email'), class: 'signup-field-label') + .signup-field-input-wrapper + %i.fa.fa-user.signup-field-icon + = f.text_field(:login, class: 'signup-field-input', placeholder: t('.login_or_email')) + + .signup-field-group + = f.label(:password, t('.password'), class: 'signup-field-label') + .signup-field-input-wrapper + %i.fa.fa-lock.signup-field-icon + = f.password_field(:password, class: 'signup-field-input', placeholder: t('.password')) + + .signin-remember-me + %label.signin-checkbox-label = f.check_box :remember_me = t('.remember_me') + - if @ask_for_recaptcha - .control-group + .signup-field-group = render 'shared/captcha' - .actions - %input.btn.btn-primary{ type: 'submit', value: t('.log_in') } + + .signup-field-actions + %input.signup-submit-btn{ type: 'submit', value: t('.log_in') } diff --git a/app/views/sessions/new.html.haml b/app/views/sessions/new.html.haml index efc567bfe..dca5bc491 100644 --- a/app/views/sessions/new.html.haml +++ b/app/views/sessions/new.html.haml @@ -1,40 +1,56 @@ - content_for(:html_title) { t '.page_title' } +- page_context[:suppress_flash] = true -.sessions-new{ style: 'margin-top: 1em;' } - .col-xs-6.col-sm-7 - %h2= t('.page_header') - %p= t('.account_help1') - .well#sign-in-text - %ul - %li= t('.account_help2') - %li= t('.account_help3') - %li= t('.account_help4') - %p - %a.btn.btn-success{ href: new_account_path(invite: params[:invite]) }= t('.register_today') - != t('.register_today_cta') - .col-xs-6.col-sm-5 - - if request.get? - #sign-in-options - = link_to 'javascript:', { class: 'btn btn-primary btn-block github-oauth' }.merge(github_data_attributes) do - = t('.sign_in_with') - %br - %i.fa.fa-github - Github - - = link_to 'javascript:', id: 'sign-in-email', class: 'btn btn-primary btn-block' do - = t('.sign_in_with') - %br - %i.fa.fa-envelope-o - Email & Password +.signup-page + / ── Left Panel ────────────────────────────────────────── + = render 'layouts/partials/auth_left_panel', + hero_lines: [t('.hero_welcome'), t('.hero_back_to')], + hero_accent: t('.hero_accent'), + tagline: t('.tagline') - %div{ id: ('sign-in-fields' if request.get?) } - = link_to 'javascript:', { class: 'btn btn-primary btn-block github-oauth' }.merge(github_data_attributes) do - = t('.sign_in_with') + / ── Right Panel ───────────────────────────────────────── + .signup-right-panel + = render partial: 'layouts/partials/alert' + .signup-form-card + / GitHub button + %button{ { type: 'button', class: 'btn-github github-oauth' }.merge(github_data_attributes) } %i.fa.fa-github - Github - = render 'sessions/sign_in' - .inset - %p - %a.command{ href: new_password_path }= t('.forgot_password') - %br - %a.command{ href: new_activation_resend_path }= t('.resend_activation_email') + = t('.sign_in_with') + GitHub + + / Divider + .signup-divider + %span.signup-divider__line + %span.signup-divider__text= t('.divider') + %span.signup-divider__line + + / Heading + .signup-form-header + %h2.signup-form-title= t('.form_title') + %p.signup-form-subtitle + = t('.no_account') + = link_to t('.join_now'), new_account_path, class: 'signup-signin-link' + + / Email toggle button (shown on GET request) + - if request.get? + #sign-in-options + %button#sign-in-email.btn-email-signup{ type: 'button' } + %i.fa.fa-envelope-o + = t('.sign_in_with_email') + + / Form fields — hidden on GET, shown after email click or on POST errors + %div{ id: ('sign-in-fields' if request.get?) } + = render 'sessions/sign_in' + + / Helper links + .signin-helper-links + = link_to t('.forgot_password'), new_password_path, class: 'signin-helper-link signin-helper-link--primary' + = link_to t('.resend_activation_email'), new_activation_resend_path, class: 'signin-helper-link' + + / Terms + .signup-terms-note + = t('.terms_preamble') + = link_to t('.terms_of_service'), 'https://community.blackduck.com/s/article/Black-Duck-Open-Hub-Terms-of-Use', class: 'signup-terms-link', target: '_blank', rel: 'noopener noreferrer' + and + = link_to t('.privacy_policy'), 'https://www.blackduck.com/privacy.html', class: 'signup-terms-link', target: '_blank', rel: 'noopener noreferrer' + \. diff --git a/app/views/shared/_alert.html.haml b/app/views/shared/_alert.html.haml index 983dca124..16082db45 100644 --- a/app/views/shared/_alert.html.haml +++ b/app/views/shared/_alert.html.haml @@ -1,4 +1,8 @@ -.alert.alert-info.alert-dismissible - %span.glyphicon.icon-info-sign{ 'aria-hidden' => true } - %button.close{ data: { dismiss: 'alert' } } x - = message +.alert.alert-info.modern-alert{ role: 'alert' } + .alert-accent-strip + .alert-inner + .flash-alert-icon + %i.fa.fa-info-circle{ aria: { hidden: 'true' } } + .flash-alert-content= message + %button.close.flash-close{ type: 'button', aria: { label: 'Close' } } + %i.fa.fa-times diff --git a/app/views/shared/_analysis_timestamp.html.haml b/app/views/shared/_analysis_timestamp.html.haml index a534b9772..39730716d 100644 --- a/app/views/shared/_analysis_timestamp.html.haml +++ b/app/views/shared/_analysis_timestamp.html.haml @@ -1,4 +1,4 @@ -.pull-right.soft#analysis_timestamp +.pull-left.soft#analysis_timestamp %i - unless analysis.nil? %i.icon-time diff --git a/app/views/shared/_api_outage.html.haml b/app/views/shared/_api_outage.html.haml index 89577433b..89d133459 100644 --- a/app/views/shared/_api_outage.html.haml +++ b/app/views/shared/_api_outage.html.haml @@ -1,5 +1,8 @@ -.alert.alert-warning.alert-block - %h4.alert-heading.nomargin= t('.heading') - %p= t('.description_1') - = t('.description_2') - %p= link_to t('.help_forum'), 'https://community.blackduck.com/s/topic/0TO2H000000gHS1WAM/black-duck-open-hub-help' +.alert.alert-warning.modern-alert.alert-block + .alert-accent-strip + .alert-inner + .alert-block-content + %h3= t('.heading') + %p= t('.description_1') + %p= t('.description_2') + %p= link_to t('.help_forum'), 'https://community.blackduck.com/s/topic/0TO2H000000gHS1WAM/black-duck-open-hub-help', target: '_blank', rel: 'noopener noreferrer' diff --git a/app/views/shared/_creativecommons_license.html.haml b/app/views/shared/_creativecommons_license.html.haml index 36aa52032..4777363bc 100644 --- a/app/views/shared/_creativecommons_license.html.haml +++ b/app/views/shared/_creativecommons_license.html.haml @@ -1,2 +1,2 @@ -%a{ href: 'http://creativecommons.org/licenses/by/3.0/', rel: 'license', target: '_blank' } +%a{ href: 'http://creativecommons.org/licenses/by/3.0/', rel: 'license noopener noreferrer', target: '_blank' } %img{ alt: 'Creative Commons License', src: '//i.creativecommons.org/l/by/3.0/88x31.png' } diff --git a/app/views/shared/_global_search.html.haml b/app/views/shared/_global_search.html.haml new file mode 100644 index 000000000..50d54eb19 --- /dev/null +++ b/app/views/shared/_global_search.html.haml @@ -0,0 +1,16 @@ +/ Global search bar with Projects / People / Organizations filter dropdown +/ Usage: = render 'shared/global_search', placeholder: 'Search projects...' +- search_placeholder = local_assigns.fetch(:placeholder, t('shared.global_search.placeholder')) + +%form.header-search-form{ action: projects_path, method: :get, role: 'search' } + .btn-group.ux-dropdown.header-search-dropdown + %button.search-filter-btn.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' } + %span.selection{ val: 'p' }= t('shared.global_search.projects') + %i.fa.fa-chevron-down + .dropdown-menu.search-dropdown-menu + %button.dropdown-item.active{ type: 'button', val: 'p' }= t('shared.global_search.projects') + %button.dropdown-item{ type: 'button', val: 'people' }= t('shared.global_search.people') + %button.dropdown-item{ type: 'button', val: 'orgs' }= t('shared.global_search.organizations') + = text_field_tag :query, params[:query], placeholder: search_placeholder, class: 'header-search-input', 'aria-label': t('shared.global_search.search') + %button.header-search-btn{ type: 'submit', 'aria-label': t('shared.global_search.search') } + %i.fa.fa-search diff --git a/app/views/shared/_modern_pagination.html.haml b/app/views/shared/_modern_pagination.html.haml new file mode 100644 index 000000000..d4e07ffd7 --- /dev/null +++ b/app/views/shared/_modern_pagination.html.haml @@ -0,0 +1,24 @@ +- if collection.respond_to?(:total_pages) && collection.total_pages > 1 + .modern-pagination + - current_page = collection.current_page + - total_pages = collection.total_pages + %button.page-btn{disabled: current_page == 1, onclick: "window.location.href='?#{request.query_parameters.merge(page: (current_page - 1)).to_query}'", aria: { label: 'Previous page' }} + %i.fa.fa-chevron-left{ aria: { hidden: 'true' } } + - visible_pages = [] + - window_size = 2 + - visible_pages << 1 + - if total_pages > 1 + - left = [current_page - window_size, 2].max + - right = [current_page + window_size, total_pages - 1].min + - visible_pages << :gap if left > 2 + - visible_pages.concat((left..right).to_a) if left <= right + - visible_pages << :gap if right < total_pages - 1 + - visible_pages << total_pages + - visible_pages.each do |page| + - if page == :gap + %span.page-ellipsis … + - else + %button.page-btn{class: ('active' if page == current_page), onclick: "window.location.href='?#{request.query_parameters.merge(page: page).to_query}'"} + = page + %button.page-btn{disabled: current_page == total_pages, onclick: "window.location.href='?#{request.query_parameters.merge(page: (current_page + 1)).to_query}'", aria: { label: 'Next page' }} + %i.fa.fa-chevron-right{ aria: { hidden: 'true' } } diff --git a/app/views/shared/_page_loader.html.haml b/app/views/shared/_page_loader.html.haml new file mode 100644 index 000000000..379c24f48 --- /dev/null +++ b/app/views/shared/_page_loader.html.haml @@ -0,0 +1,5 @@ +/ Page loader - hidden by default, shown via JS when navigating to slow pages +#page-loader.page-loader.hidden + .loader-content{ role: 'status', 'aria-live': 'polite' } + %i.fa.fa-spinner.fa-spin{ 'aria-hidden': 'true' } + %p= t('shared.page_loader.loading') diff --git a/app/views/shared/_search.html.haml b/app/views/shared/_search.html.haml index 62493903a..a07554584 100644 --- a/app/views/shared/_search.html.haml +++ b/app/views/shared/_search.html.haml @@ -3,22 +3,17 @@ global_search_param = '' if @avoid_global_search - unless request.path == root_path - %form.pull-right#quicksearch{ action: projects_path } - .dropdown - .btn-group.ux-dropdown - %a.btn.btn-small.dropdown-toggle{ 'data-toggle' => 'dropdown' } - %span.selection= t('projects_menu') - %span.caret - %ul.dropdown-menu - %li - %a{ val: 'people' }= t('people_menu') - %li - %a.default{ val: 'p' }= t('projects_menu') - %li - %a{ val: 'orgs' }= t('organizations_menu') - %input.search.text.global_top_search{ type: :text, name: 'query', placeholder: t('shared.search.search_text'), - value: global_search_param, autocomplete: 'off' } - %input.search.hidden{ type: 'hidden', name: 'search_type', id: 'search_type', value: 'projects' } - %button.submit.no_padding{ type: 'submit' } - .icon-search.global_top_search_icon + %form#header-search-form.header-search-form{ action: projects_path } + .btn-group.ux-dropdown.header-search-dropdown + %button.search-filter-btn.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' } + %span.selection{ val: 'p' }= t('projects_menu') + %i.fa.fa-chevron-down + .dropdown-menu.search-dropdown-menu + %button.dropdown-item.active{ type: 'button', val: 'p' }= t('projects_menu') + %button.dropdown-item{ type: 'button', val: 'people' }= t('people_menu') + %button.dropdown-item{ type: 'button', val: 'orgs' }= t('organizations_menu') + %input.header-search-input{ type: 'text', name: 'query', placeholder: t('shared.search.search_text'), value: global_search_param, autocomplete: 'off', 'aria-label': t('shared.search.search') } + %input.search.hidden{ type: 'hidden', name: 'search_type', id: 'search_type', value: 'projects' } + %button.header-search-btn{ type: 'submit', 'aria-label': t('shared.search.search') } + %i.fa.fa-search diff --git a/app/views/shared/_search_dingus.html.haml b/app/views/shared/_search_dingus.html.haml index 496109b75..fbc0618c2 100644 --- a/app/views/shared/_search_dingus.html.haml +++ b/app/views/shared/_search_dingus.html.haml @@ -6,13 +6,13 @@ sort_context ||= nil no_match_found_type ||= :message -.margin_top_20#search-dingus - %form.form-inline - .row - = render 'shared/search_dingus/page_entries_info', collection: collection, total_count: total_count - +.search-filter-bar + %form + .search-filter-content = render 'shared/search_dingus/search_bar', search_type: search_type, tags: tags = render 'shared/search_dingus/sort', filter_type: filter_type, sort_context: sort_context += render 'shared/search_dingus/page_entries_info', collection: collection, total_count: total_count + = render 'shared/search_dingus/no_match_found', collection: collection, no_match_found_type: no_match_found_type diff --git a/app/views/shared/search_dingus/_commits_or_contributor_search.html.haml b/app/views/shared/search_dingus/_commits_or_contributor_search.html.haml index 9677df480..967215a23 100644 --- a/app/views/shared/search_dingus/_commits_or_contributor_search.html.haml +++ b/app/views/shared/search_dingus/_commits_or_contributor_search.html.haml @@ -1,23 +1,29 @@ - total_count ||= 0 -- if type == :commits && @project.best_analysis.oldest_code_set_time.present? - %span.commits_display_date_range.pull-right - %i.icon-time - - if TIME_SPANS[params[:time_span]] == :last_30_days - = (@project.best_analysis.oldest_code_set_time - 30.days).to_s(:mdy) - - else - = (@project.best_analysis.oldest_code_set_time - 12.months).to_s(:mdy) - — - = @project.best_analysis.oldest_code_set_time.to_s(:mdy) -.margin_top_20#search-dingus - %form.form-inline - .row +.search-filter-bar + %form + .search-filter-content = render 'shared/search_dingus/page_entries_info', collection: items, total_count: total_count - .col-md-5.no_padding - %label #{t('shared.search_dingus.search_bar.search_text')}   - = text_field_tag :query, params[:query] - = hidden_field_tag :time_span, params[:time_span] - %button.btn.btn-refresh{ type: 'Submit' } - %i.icon-refresh + + .search-section + %label{for: 'query'}= t('shared.search_dingus.search_bar.search_text') + .search-input-wrapper + %input#query{type: 'text', name: 'query', placeholder: t('shared.search_dingus.search_bar.search_text'), value: params[:query]} + = hidden_field_tag :time_span, params[:time_span] + %button.search-cancel-btn{ type: 'button', 'aria-label': t('shared.search_dingus.search_bar.clear_search'), style: (params[:query].blank? ? 'display: none;' : '') } + %i.fa.fa-times + %button.search-refresh-btn{ type: 'submit', 'aria-label': t('shared.search_dingus.search_bar.search') } + %i.fa.fa-refresh + - if type == :contributions = render 'shared/search_dingus/sort', sort_context: :contributions, filter_type: nil + + - if type == :commits && @project.best_analysis.oldest_code_set_time.present? + %span.commits_display_date_range + %i.icon-time + - if TIME_SPANS[params[:time_span]] == :last_30_days + = (@project.best_analysis.oldest_code_set_time - 30.days).to_s(:mdy) + - else + = (@project.best_analysis.oldest_code_set_time - 12.months).to_s(:mdy) + — + = @project.best_analysis.oldest_code_set_time.to_s(:mdy) diff --git a/app/views/shared/search_dingus/_page_entries_info.html.haml b/app/views/shared/search_dingus/_page_entries_info.html.haml index f0bb5827a..985647b93 100644 --- a/app/views/shared/search_dingus/_page_entries_info.html.haml +++ b/app/views/shared/search_dingus/_page_entries_info.html.haml @@ -1,7 +1,7 @@ -.col-md-3.col-sm-3.col-xs-3.padding_left_25 +.pagination-info - if collection.respond_to?(:current_page) && !collection.length.zero? - %label.paginate= t('.pagination_status', current_page: number_with_delimiter(collection.current_page), - total_pages: number_with_delimiter(collection.total_pages)) + = t('.pagination_status', + current_page: number_with_delimiter(collection.current_page), + total_pages: number_with_delimiter(collection.total_pages)) - elsif !total_count.to_i.zero? - %label.paginate= t('.count_status', current_count: collection.length, - total_count: number_with_delimiter(total_count)) + = t('.count_status', current_count: collection.length, total_count: number_with_delimiter(total_count)) diff --git a/app/views/shared/search_dingus/_search_bar.html.haml b/app/views/shared/search_dingus/_search_bar.html.haml index acc25067e..3d2febd3f 100644 --- a/app/views/shared/search_dingus/_search_bar.html.haml +++ b/app/views/shared/search_dingus/_search_bar.html.haml @@ -1,5 +1,5 @@ - if search_type == :tags - .search_tags + .search-section %label= t('.filter_by_tags') .chosen#value_select = select_tag :names, options_for_select(tags.map { |ary| ary.map(&:fix_encoding_if_invalid) }, params[:names]), @@ -7,9 +7,11 @@ multiple: true, onchange: 'this.form.submit()' - else - .col-md-5.col-sm-5.col-xs-5.no_padding - %label #{t('.search_text')}   - = text_field_tag :query, params[:query] - - %button.btn.btn-refresh{ type: 'Submit' } - %i.icon-refresh + .search-section + %label{for: 'query'}= t('.search_text') + .search-input-wrapper + %input#query{type: 'text', name: 'query', placeholder: t('.search_text'), value: params[:query]} + %button.search-cancel-btn{ type: 'button', 'aria-label': t('.clear_search'), style: (params[:query].blank? ? 'display: none;' : '') } + %i.fa.fa-times + %button.search-refresh-btn{ type: 'submit', 'aria-label': t('.search') } + %i.fa.fa-refresh diff --git a/app/views/shared/search_dingus/_sort.html.haml b/app/views/shared/search_dingus/_sort.html.haml index b3e99c0a7..24b0a5cdb 100644 --- a/app/views/shared/search_dingus/_sort.html.haml +++ b/app/views/shared/search_dingus/_sort.html.haml @@ -1,28 +1,45 @@ -.col-md-4.col-sm-4.col-xs-4 - - if filter_type == :checkbox - %label.checkbox.pull-right.padding_right_20#human_edits - %input{ type: 'checkbox', checked: params[:human] == 'true' } - = t('.show_only_humans') - %label.checkbox.pull-right.padding_right_15#enlistment_edits - %input{ type: 'checkbox', id: 'enlistment_checkbox', checked: params[:enlistment] == 'true'} - = t('.filter_only_enlistment') - #sort_by - %label= t('.sort_by') - = select_tag :sort, options_for_select([["updated_at", "updated_at"], ["created_at", "created_at"]], - params[:sort]), - class: 'chzn-select', onchange: 'this.form.submit()' +- if filter_type == :checkbox + %label.checkbox#human_edits + %input{ type: 'checkbox', checked: params[:human] == 'true' } + = t('.show_only_humans') + %label.checkbox#enlistment_edits + %input{ type: 'checkbox', id: 'enlistment_checkbox', checked: params[:enlistment] == 'true'} + = t('.filter_only_enlistment') + .sort-section + %label= t('.sort_by') + :ruby + sort_options = [["updated_at", "updated_at"], ["created_at", "created_at"]] + current_sort = params[:sort] || "updated_at" + selected_label = sort_options.find { |opt| opt[1] == current_sort }&.first || sort_options.first.first + .custom-sort-dropdown + %button.sort-dropdown-btn{ type: 'button' } + %span.selection{ data: { value: current_sort } }= selected_label + %i.fa.fa-chevron-down + .dropdown-menu.sort-dropdown-menu + - sort_options.each do |label, value| + %button.sort-dropdown-item{ type: 'button', data: { value: value }, class: (value == current_sort ? 'active' : '') }= label + %input{ type: 'hidden', name: 'sort', value: current_sort, class: 'sort-value-input' } - - elsif filter_type == :radiobutton - %label.radio.inline.margin_left_40 +- elsif filter_type == :radiobutton + .search-section + %label.radio.inline = radio_button_tag('find_by', 'name', params[:find_by] != 'email') = t('.by_name') %label.radio.inline = radio_button_tag('find_by', 'email', params[:find_by] == 'email') = t('.by_email') - - elsif sort_context.present? - #sort_by - %label= t('.sort_by') - = select_tag :sort, options_for_select(SORT_OPTIONS[sort_context][:options], - params[:sort] || SORT_OPTIONS[sort_context][:default]), - class: 'chzn-select', onchange: 'this.form.submit()' +- elsif sort_context.present? + .sort-section + %label= t('.sort_by') + :ruby + current_sort = params[:sort] || SORT_OPTIONS[sort_context][:default] + selected_label = SORT_OPTIONS[sort_context][:options].find { |opt| opt[1] == current_sort }&.first || SORT_OPTIONS[sort_context][:options].first.first + .custom-sort-dropdown + %button.sort-dropdown-btn{ type: 'button' } + %span.selection{ data: { value: current_sort } }= selected_label + %i.fa.fa-chevron-down + .dropdown-menu.sort-dropdown-menu + - SORT_OPTIONS[sort_context][:options].each do |label, value| + %button.sort-dropdown-item{ type: 'button', data: { value: value }, class: (value == current_sort ? 'active' : '') }= label + %input{ type: 'hidden', name: 'sort', value: current_sort, class: 'sort-value-input' } diff --git a/app/views/stack_entries/_modal.html.haml b/app/views/stack_entries/_modal.html.haml index c0708608b..87ed3a801 100644 --- a/app/views/stack_entries/_modal.html.haml +++ b/app/views/stack_entries/_modal.html.haml @@ -1,37 +1,44 @@ .modal.fade#stack-entries-modal{ 'aria-hidden' => 'true', 'aria-labelledby' => 'iUseThisModalLabel', :role => 'dialog', :tabindex => '-1' } - .modal-dialog - .modal-content - .modal-header - %button.close{ 'aria-label' => 'Close', 'data-dismiss' => 'modal', :type => 'button' } + .modal-dialog.modal-dialog-centered.stack-entries-dialog + .modal-content.stack-entries-content + .modal-header.stack-entries-header + %h4.modal-title.stack-entries-title#iUseThisModalLabel + %i.fa.fa-list-ul{ 'aria-hidden' => 'true' } + = t('.add_to_stacks') + %button.close.stack-entries-close{ 'aria-label' => 'Close', 'data-dismiss' => 'modal', :type => 'button' } %span{ 'aria-hidden' => 'true' } × - %h4.modal-title#myModalLabel= t('.add_to_stacks') - .modal-body - .text-center.margin_bottom_5 + .modal-body.stack-entries-body + %p.stack-entries-subtitle = t('.add_to_your_stack', project: @project.name) - - current_user.stacks.each do |stack| - .stack-checkbox-container.row - .stack-checkbox.col-md-4.col-md-offset-3 - - stack_entry = stack.stack_entries.where(project_id: @project.id).take - = check_box_tag 'stacked', 0, false, 'data-stack-id' => stack.to_param, - 'data-project-url-name' => @project.to_param, - 'data-stack-entry-id' => stack_entry.to_param - %span.margin_left_5 - = stack.title - .col-md-1 - = image_tag 'spinner.gif', class: 'hidden spinner' - .col-md-4.message + .stack-entries-list + - current_user.stacks.each do |stack| + .stack-entries-row + .stack-entries-row-left + - stack_entry = stack.stack_entries.where(project_id: @project.id).take + .stack-entries-checkbox-wrap + = check_box_tag 'stacked', 0, false, + class: 'stack-entries-checkbox', + 'data-stack-id' => stack.to_param, + 'data-project-url-name' => @project.to_param, + 'data-stack-entry-id' => stack_entry.to_param + %label.stack-entries-label{ for: 'stacked' } + %i.fa.fa-th-large.stack-entries-stack-icon{ 'aria-hidden' => 'true' } + = stack.title + .stack-entries-row-right + = image_tag 'spinner.gif', class: 'hidden spinner stack-entries-spinner', alt: 'Loading' + %span.stack-entries-message - .modal-footer - .text-center - - stack = Stack.new - - stack.stack_entries.build(project_id: @project.id) + .modal-footer.stack-entries-footer + - stack = Stack.new + - stack.stack_entries.build(project_id: @project.id) - = form_for stack do |f| - = hidden_field_tag :project_id, @project.id - = f.fields_for :stack_entries do |stack_entry_form| - = stack_entry_form.hidden_field :project_id + = form_for stack, html: { class: 'stack-entries-new-form' } do |f| + = hidden_field_tag :project_id, @project.id + = f.fields_for :stack_entries do |stack_entry_form| + = stack_entry_form.hidden_field :project_id - = f.submit t('.new_stack', project: @project.name), class: 'btn btn-primary btn-lg stack-align' + = f.submit t('.new_stack', project: @project.name), + class: 'btn btn-primary btn-block stack-entries-submit' diff --git a/app/views/stack_widgets/_normal_css.html.haml b/app/views/stack_widgets/_normal_css.html.haml index 3c1ce9b32..8a77a3b43 100644 --- a/app/views/stack_widgets/_normal_css.html.haml +++ b/app/views/stack_widgets/_normal_css.html.haml @@ -25,6 +25,17 @@ .icons margin: 0 padding: 0 + display: flex + flex-wrap: wrap + gap: 2px + .icon-container + width: 20px + height: 20px + border-radius: 3px + overflow: hidden + box-shadow: none + .icon-letter + font-size: 12px p margin: 0 text-align: right diff --git a/app/views/stack_widgets/_normal_inner.html.haml b/app/views/stack_widgets/_normal_inner.html.haml index 995b63e4d..ac505ea72 100644 --- a/app/views/stack_widgets/_normal_inner.html.haml +++ b/app/views/stack_widgets/_normal_inner.html.haml @@ -12,4 +12,4 @@ - if widget.more > 0 %p.promo-more-link= t('.more', more: widget.more) .ohloh - = image_tag('widget_logos/openhublogo.png') + = image_tag('widget_logos/openhublogo.png', alt: 'Open Hub') diff --git a/app/views/stack_widgets/_promo.html.haml b/app/views/stack_widgets/_promo.html.haml index 300e1e9f8..695f5c066 100644 --- a/app/views/stack_widgets/_promo.html.haml +++ b/app/views/stack_widgets/_promo.html.haml @@ -1,5 +1,5 @@ - unless defined?(HIDE_BADGE) && HIDE_BADGE == true - %a.promo{ href: href } + %a.promo.oh-card{ href: href } .inset %h6= t('.embed') %p.soft= t('.show') diff --git a/app/views/stack_widgets/_widget.html.haml b/app/views/stack_widgets/_widget.html.haml index 4b0055797..8db949e31 100644 --- a/app/views/stack_widgets/_widget.html.haml +++ b/app/views/stack_widgets/_widget.html.haml @@ -3,32 +3,32 @@ script = "" .row - .col-md-4.well.badge_form + .col-md-4.oh-card.badge_form %form.styled.customized_stack_badge %fieldset .control-group - %label.control-label.required= t('.badge_title') + %label.control-label.required{ for: 'badge_title_input' }= t('.badge_title') .controls - %input.title{ type: 'text', value: widget.stack.friendly_name } + %input.title#badge_title_input{ type: 'text', value: widget.stack.friendly_name } .control-group %label.control-label= t('.icon_size') %label.radio{ for: :small_icon } - = t('.small') %input#small_icon{ type: 'radio', checked: 'checked', value: 16, name: 'icon_size' } + = t('.small') %label.radio{ for: :medium_icon } - = t('.medium') %input#medium_icon{ type: 'radio', name: 'icon_size', value: 24 } + = t('.medium') .control-group %label.control-label= t('.badge_width') .controls %label.radio %input.radio_button#pixels{ type: 'radio', name: 'badge_width_unit' } - %input.input-mini-num.pull-left{ type: 'text', size: 4, disabled: 'disabled', value: '214' } + %input.input-mini-num.pull-left{ type: 'text', size: 4, disabled: 'disabled', value: '214', 'aria-label' => t('.wide') } %span.help-inline= t('.wide') .clearfix %label.radio %input.radio_button#icons{ type: 'radio', checked: 'checked', name: 'badge_width_unit' } - %input.input-mini-num.pull-left.icons{ type: 'text', size: 4, value: widget.initial_icons_width } + %input.input-mini-num.pull-left.icons{ type: 'text', size: 4, value: widget.initial_icons_width, 'aria-label' => t('.icons') } %span.help-inline= t('.icons') .clearfix .col-md-6.margin_left_10 @@ -39,5 +39,5 @@ %p   %h4.margin_bottom_5= t('.embed') %span.help-block= t('.hint') - %pre{ id: normal_stack_widgets_url(widget.stack) } + %pre.oh-card{ id: normal_stack_widgets_url(widget.stack) } = script diff --git a/app/views/stacks/_stack_entry.html.haml b/app/views/stacks/_stack_entry.html.haml index 6171e97b7..dd87b3cca 100644 --- a/app/views/stacks/_stack_entry.html.haml +++ b/app/views/stacks/_stack_entry.html.haml @@ -25,6 +25,6 @@ %span.note= stack_entry.note - if editable %td{ width: '18%', valign: 'top' } - %a.btn.btn-mini.btn-danger.command.stack_remove{ href: '#', + %a.btn.btn-medium.btn-danger.command.stack_remove{ href: '#', id: "stackit_#{stack_entry.project.vanity_url}" } %i.icon-trash= t :remove, scope: 'stacks.stack_entry' diff --git a/app/views/stacks/index.html.haml b/app/views/stacks/index.html.haml index 285f9edd6..40e38610c 100644 --- a/app/views/stacks/index.html.haml +++ b/app/views/stacks/index.html.haml @@ -3,43 +3,51 @@ page_context[:select_top_menu_nav] = 'select_people' page_context[:select_footer_nav] = :stacks -.row - .col-md-9 - %h2.pull-left= t('.stacks') +.stacks-index-page + .stacks-page-header + %h2.stacks-page-title= t('.stacks') - if current_user == @account - %a.btn.btn-mini.btn-primary{ href: stacks_path, data: { method: :post }, - style: 'margin:8px 19px 0 0; float: right;' } - %i.icon-plus-sign= t '.new_stack' - .clear + %a.btn.btn-primary.stacks-new-btn{ href: stacks_path, data: { method: :post } } + %i.icon-plus-sign{ 'aria-hidden' => 'true' } + = t('.new_stack') -.row - .col-md-9 - - @stacks.each do |stack| - .well.piano{ id: "stack_#{stack.id}" } - - if stack.account == current_user - = icon_button(stack_path(stack), text: t('.remove'), size: 'mini', type: 'danger', - icon: 'trash', data: { confirm: t('.delete_confirm', name: stack.title) }, - method: :delete, style: 'float: right; display: block; margin-top: 10px;') - %h4.nomargin - %a.name{ href: stack_path(stack), title: stack.title }= stack.title - %a.description{ href: stack_path(stack), title: stack.title } - %p.nomargin.soft= stack.description - .clear - - stack.projects.order(Arel.sql('logo_id IS NULL, stack_entries.created_at DESC')).to_a.each do |project| - - project_link(project, :tiny, title: project.name) - %a{ href: stack_widgets_path(stack) }= t('.embed') - - if @stacks.blank? - .inset - - if current_user == @account - %p= t('.no_stack_fix_that_html', link: link_to(t('.new_stack'), stacks_path, method: :post, class: 'command')) - - else - %p= t('.no_stack') -   - .col-md-3 - .well - .inset.stack_definition - %h5.soft= t('.what_is_a_stack') - - lamp_link = link_to('LAMP', 'http://wikipedia.org/wiki/LAMP_(software_bundle)') - %p!= t('.stack_explanation', link: lamp_link) - %p= t('.max_stacks_desc') -= will_paginate @stacks + .stacks-layout + .stacks-main + - @stacks.each do |stack| + .oh-card.stacks-card{ id: "stack_#{stack.id}" } + .stacks-card-header + %h4.stacks-card-title + %a.stacks-card-name{ href: stack_path(stack), title: stack.title }= stack.title + - if stack.account == current_user + = icon_button(stack_path(stack), text: t('.remove'), size: 'medium', type: 'danger', + icon: 'trash', data: { confirm: t('.delete_confirm', name: stack.title) }, + method: :delete, style: 'float: right; display: block; margin-top: 10px;') + - if stack.description.present? + %a.stacks-card-desc-link{ href: stack_path(stack) } + %p.stacks-card-desc= stack.description + .stacks-card-projects + - stack.projects.order(Arel.sql('logo_id IS NULL, stack_entries.created_at DESC')).to_a.each do |project| + - project_link(project, :tiny, title: project.name) + .stacks-card-footer + %a.stacks-embed-link{ href: stack_widgets_path(stack) } + %i.fa.fa-code{ 'aria-hidden' => 'true' } + = t('.embed') + + - if @stacks.blank? + .stacks-empty-state + %i.fa.fa-th-large.stacks-empty-icon{ 'aria-hidden' => 'true' } + - if current_user == @account + %p.stacks-empty-text= t('.no_stack_fix_that_html', link: link_to(t('.new_stack'), stacks_path, method: :post, class: 'stacks-empty-link')) + - else + %p.stacks-empty-text= t('.no_stack') + + = render 'shared/modern_pagination', collection: @stacks + + .stacks-sidebar + .oh-card.stacks-info-card + %h5.stacks-info-title + %i.fa.fa-info-circle{ 'aria-hidden' => 'true' } + = t('.what_is_a_stack') + - lamp_link = link_to('LAMP', 'http://wikipedia.org/wiki/LAMP_(software_bundle)', target: '_blank', rel: 'noopener noreferrer') + %p.stacks-info-text!= t('.stack_explanation', link: lamp_link) + %p.stacks-info-text= t('.max_stacks_desc') diff --git a/app/views/stacks/show.html.haml b/app/views/stacks/show.html.haml index 5f5ea9935..2dd9190cb 100644 --- a/app/views/stacks/show.html.haml +++ b/app/views/stacks/show.html.haml @@ -11,11 +11,11 @@ = @stack.title - stack_edit_in_place if editable - if editable - = icon_button(stack_path(@stack), text: t('.remove'), size: 'mini', type: 'danger', icon: 'trash', + = icon_button(stack_path(@stack), text: t('.remove'), size: 'medium', type: 'danger', icon: 'trash', data: { confirm: t('.delete_confirm') }, method: :delete, style: 'float: right; display: block; margin-top: 10px;') .clear -.row{ id: "stack_#{@stack.id}" } +.row.oh-card{ id: "stack_#{@stack.id}" } .col-md-9 %p.description.nomargin %span{ class: editable ? 'rest_in_place' : '', attribute: 'description', col: 35, max_length: 120, rows: 3 } @@ -24,30 +24,35 @@ - if editable - show_them = @stack.project_count == 0 .padding_one_top - .recommendations + .recommendations.oh-card .clear{ style: ('display:none' if show_them) } %h4.soft.nomargin.show - %a.link_no_underline#show= t('.show_recommends') + %a.link_no_underline#show{ href: 'javascript:void(0)', role: 'button' } + = t('.show_recommends') + %i.fa.fa-chevron-down.pull-right .clear#recommendations{ style: ('display:none' unless show_them) } %h4.soft{ style: 'margin-bottom: 0.3em;' } = t('.recommends_for_this_stack')   - %a.command#hide= t('.hide') + %a.command#hide{ href: 'javascript:void(0)', role: 'button', 'aria-label' => t('.recommends_for_this_stack') } + %i.fa.fa-chevron-up.pull-right .list %ul.stack_sm_suggestions - if show_them = render partial: 'small_suggestion', collection: @stack.suggest_projects(5) .controls{ style: ('display:none' unless show_them) } - %a.btn.btn-mini.command#skip_all= t('.skip_all') - %a.command.btn.btn-mini#more{ style: 'display:none' }= t('.show_me_more') - %a.btn.btn-mini.clear_ignores.command{ title: t('.unignore_all') }= t('.show_all_projects') + %a.btn.btn-mini.btn-primary.command#skip_all{ href: 'javascript:void(0)', role: 'button' }= t('.skip_all') + %a.command.btn.btn-mini.btn-primary#more{ style: 'display:none', href: 'javascript:void(0)', role: 'button' }= t('.show_me_more') + %a.btn.btn-mini.btn-primary.clear_ignores.command{ title: t('.unignore_all'), href: 'javascript:void(0)', role: 'button' }= t('.show_all_projects') .clear   = form_for StackEntry.new, as: :stack_entry, url: stack_stack_entries_path(@stack), html: { class: 'form-inline', id: 'add_project_form' } do |f| - source_url = "/accounts/#{current_user.id}/autocompletes/#{@stack.id}/projects_for_stack" - = f.text_field :project_name, size: 20, class: 'autocompletable', - data: { source: source_url, select: 'submitForm' } - %input.btn.btn-small.btn-primary{ type: 'submit', id: 'new_stack_entry', value: 'Add Project to Stack' } + .form-group + = f.label :project_name, 'Add Project to Stack', class: 'control-label' + = f.text_field :project_name, class: 'autocompletable form-control', + data: { source: source_url, select: 'submitForm' } + %input.btn.btn-primary{ type: 'submit', id: 'new_stack_entry', value: 'Add Project to Stack' } = f.hidden_field :stack_id, value: @stack.id .stack_item_list %table.table.table-bordered.stack_list{ id: "stack_list_#{@stack.id}" } @@ -60,12 +65,12 @@ = render 'sample_stacks', stack: @stack - else %p= t('.empty_stack_msg') - = will_paginate @stack_entries + = render 'shared/modern_pagination', collection: @stack_entries .col-md-3 - .well + .oh-card .inset.stack_definition %h5.soft= t('.what_is_a_stack') - - lamp_link = link_to('LAMP', 'http://wikipedia.org/wiki/LAMP_(software_bundle)') + - lamp_link = link_to('LAMP', 'http://wikipedia.org/wiki/LAMP_(software_bundle)', target: '_blank', rel: 'noopener noreferrer') %p!= t('.stack_explanation', link: lamp_link) %p - if @stack.project_count == 0 diff --git a/app/views/stacks/similar.html.haml b/app/views/stacks/similar.html.haml index 5850d912d..6087f676d 100644 --- a/app/views/stacks/similar.html.haml +++ b/app/views/stacks/similar.html.haml @@ -9,7 +9,7 @@ - @similar_stacks.each do |similar_stack| - account = similar_stack[:stack].account - .well.col-md-5{ style: 'margin: 0 0 20px 20px;' } + .oh-card.oh-card--hoverable.col-md-5{ style: 'margin: 0 0 20px 20px;' } .account.clear .avtr= avatar_for account, size: 60 .a_left{ style: 'margin-left: 80px' } diff --git a/app/views/tags/cloud.html.haml b/app/views/tags/cloud.html.haml index b356ad532..12915508f 100644 --- a/app/views/tags/cloud.html.haml +++ b/app/views/tags/cloud.html.haml @@ -1,27 +1,30 @@ %meta{ name: 'ROBOTS', content: 'NOINDEX, NOFOLLOW' } - content_for(:html_title) { t('.page_title') } -- content_for :session_projects_banner do - .navbar.container#sp_menu - = render 'session_projects/menu' -.tags - %h1.margin_bottom_20.margin_top_20= t('.tags') +#tag-cloud-page + .row.margin_left_30 + .col-md-10 + %h1.margin_bottom_20= t('.tags') - .col-md-10 - %p= t('.help1') - %p= t('.help2') + .oh-card.tag-cloud-search-card + .tag-cloud-help + %p= t('.help1') + %p.margin_bottom_0= t('.help2') - %form.col-md-10.autocomplete-submit{ rel: 'tag_jump' } - %fieldset - .control-group - %label.control-label= t('.input_label') - .controls - %input.text.tag_autocomplete.autocompletable#input_tag{ type: :text, - data: { source: '/autocompletes/tags', select: 'submitForm' } } - %p.help-block.margin_bottom_25= t('.input_hint') + %form.autocomplete-submit.margin_top_20{ rel: 'tag_jump' } + %fieldset + .control-group + %label.control-label{ for: 'input_tag' }= t('.input_label') + .controls + %input.text.tag_autocomplete.autocompletable.form-control#input_tag{ type: :text, + data: { source: '/autocompletes/tags', select: 'submitForm' } } + %p.help-block= t('.input_hint') - .col-md-12 - - @tags.each do |tag| - %span.col-md-2 - %a.tag.add{ href: tags_path(names: tag.name) } #{tag.name} (#{tag.taggings_count}) - = will_paginate @tags + .oh-card.tag-cloud-list-card + .tag-cloud-grid + - @tags.each do |tag| + %a.tag-cloud-item{ href: tags_path(names: tag.name) } + = tag.name + %span.tag-cloud-count= "(#{tag.taggings_count})" + .margin_top_20 + = render 'shared/modern_pagination', collection: @tags diff --git a/app/views/tags/index.html.haml b/app/views/tags/index.html.haml index 33aa9a067..5b18e831c 100644 --- a/app/views/tags/index.html.haml +++ b/app/views/tags/index.html.haml @@ -1,27 +1,34 @@ %meta{ name: 'ROBOTS', content: 'NOINDEX, NOFOLLOW' } - names = params[:names].split.flatten -- content_for(:html_title) { t('.page_title', names: names.collect { |n| "‘#{n}’" }.to_sentence) } +- content_for(:html_title) { t('.page_title', names: names.collect { |n| "'#{n}'" }.to_sentence) } - content_for :session_projects_banner do - .navbar.container#sp_menu + #sp_menu = render 'session_projects/menu' +#tag-index-page + .row.margin_left_30 + .col-md-11 -#tags_show_header - %h1 - %a{ href: tags_path }= t('.tags') - \: #{t('.browse_projects')} + .tag-index-header + %h1 + %a.tag-index-breadcrumb{ href: tags_path }= t('.tags') + %span.tag-index-breadcrumb-sep / + = t('.browse_projects') + .tag-index-pills + - names.each do |name| + %span.tag-index-pill= name -.call_to_action{ style: 'padding-top: 5px;' } - - if @projects.any? - %p= t('.select_a_tag') - - else - .margin_left_10 - %p= t('.no_projects', names: names.to_sentence) - %a{ href: tags_path }= t('.browse_tags') + .oh-card.tag-index-body + - if @projects.any? + %p.tag-index-hint= t('.select_a_tag') + - else + .tag-index-empty + %p= t('.no_projects', names: names.to_sentence) + %a.btn.btn-default{ href: tags_path }= t('.browse_tags') -= render partial: 'shared/search_dingus', locals: { collection: @projects, search_type: :tags, tags: @related_tags } + = render partial: 'shared/search_dingus', locals: { collection: @projects, search_type: :tags, tags: @related_tags } -#projects_index_list - - if @projects.any? - = render partial: 'projects/project_index', collection: @projects, - locals: { compare: true, show_active_committers: true } - = will_paginate @projects + #projects_index_list + - if @projects.any? + = render partial: 'projects/project_index', collection: @projects, + locals: { compare: true, show_active_committers: true } + = render 'shared/modern_pagination', collection: @projects diff --git a/app/views/topics/show.html.haml b/app/views/topics/show.html.haml index 2a6c85725..329700a05 100644 --- a/app/views/topics/show.html.haml +++ b/app/views/topics/show.html.haml @@ -8,9 +8,9 @@ .col-md-12 = render partial: 'posts/post', collection: @posts - = will_paginate @posts + = render 'shared/modern_pagination', collection: @posts - .well + .oh-card - if @topic.closed? %br - if @topic.account == current_user diff --git a/app/views/vulnerabilities/_vulnerability_table.html.haml b/app/views/vulnerabilities/_vulnerability_table.html.haml index ce569322c..caeb4b744 100644 --- a/app/views/vulnerabilities/_vulnerability_table.html.haml +++ b/app/views/vulnerabilities/_vulnerability_table.html.haml @@ -1,45 +1,46 @@ - if @vulnerabilities.present? - %table.table.table-striped - %thead - %tr - %th.col-md-1 - = t('vulnerabilities.index.identifier') - = render_sort_icon('cve_id') - - if @bdsa_visible - %th.col-md-1= t('vulnerabilities.index.related_record') - %th.col-md-1{ style: 'min-width: 95px;' } - = t('vulnerabilities.index.severity') - = render_sort_icon('severity') - %th.col-md-1 - = t('vulnerabilities.index.date_published') - = render_sort_icon('published_on') - %th.col-md-5= t('vulnerabilities.index.description') - %th.col-md-3= t('vulnerabilities.index.versions_affected') - %tbody - - @vulnerabilities.each do |vulnerability| + .table-responsive + %table.table.table-striped.table-condensed + %thead %tr - %td= link_to(vulnerability.cve_id, vulnerability_detail_page_url(vulnerability.cve_id), target: '_blank', rel: 'noopener') + %th.col-md-1 + = t('vulnerabilities.index.identifier') + = render_sort_icon('cve_id') - if @bdsa_visible - - related_record = vulnerability_related_record(vulnerability.cve_id, @release.vulnerabilities.pluck(:cve_id)) - %td= link_to(related_record, vulnerability_detail_page_url(related_record), target: '_blank', rel: 'noopener') if related_record - %td= vulnerability.severity.try(:titleize) - %td= vulnerability.published_on.to_s(:mdy) - %td.vul_description - - if vulnerability.description - %span#read_less - - vuln_length = vulnerability.description.length - - vuln_length_count = Vulnerability::DESC_LENGTH - = vulnerability.description[0..Vulnerability::DESC_LENGTH] - = link_to t('vulnerabilities.index.more'), 'javascript:void(0)' if vuln_length > vuln_length_count - %span#read_more{ style: 'display: none;' } - = vulnerability.description - = link_to t('vulnerabilities.index.less'), 'javascript:void(0)' - %td - .versions_affected= vulnerability[:recent_releases] unless 'BDSA'.in?(vulnerability.cve_id) + %th.col-md-1= t('vulnerabilities.index.related_record') + %th.col-md-1{ style: 'min-width: 95px;' } + = t('vulnerabilities.index.severity') + = render_sort_icon('severity') + %th.col-md-1 + = t('vulnerabilities.index.date_published') + = render_sort_icon('published_on') + %th.col-md-5= t('vulnerabilities.index.description') + %th.col-md-3= t('vulnerabilities.index.versions_affected') + %tbody + - @vulnerabilities.each do |vulnerability| + %tr + %td= link_to(vulnerability.cve_id, vulnerability_detail_page_url(vulnerability.cve_id), target: '_blank', rel: 'noopener') + - if @bdsa_visible + - related_record = vulnerability_related_record(vulnerability.cve_id, @release.vulnerabilities.pluck(:cve_id)) + %td= link_to(related_record, vulnerability_detail_page_url(related_record), target: '_blank', rel: 'noopener') if related_record + %td= vulnerability.severity.try(:titleize) + %td= vulnerability.published_on.to_s(:mdy) + %td.vul_description + - if vulnerability.description + %span#read_less + - vuln_length = vulnerability.description.length + - vuln_length_count = Vulnerability::DESC_LENGTH + = vulnerability.description[0..Vulnerability::DESC_LENGTH] + = link_to t('vulnerabilities.index.more'), 'javascript:void(0)' if vuln_length > vuln_length_count + %span#read_more{ style: 'display: none;' } + = vulnerability.description + = link_to t('vulnerabilities.index.less'), 'javascript:void(0)' + %td + .versions_affected= vulnerability[:recent_releases] unless 'BDSA'.in?(vulnerability.cve_id) .text-center - = will_paginate @vulnerabilities, class: 'oh_pagination', params: { controller: 'vulnerabilities', action: 'filter' } + = render 'shared/modern_pagination', collection: @vulnerabilities - else .no_vulnerability= t('vulnerabilities.index.no_vulnerability') .overlay-loader - %img.loader{ src: image_path('ui/loadingAnimation.gif') } + %img.loader{ src: image_path('ui/loadingAnimation.gif'), alt: '' } diff --git a/app/views/vulnerabilities/index.html.haml b/app/views/vulnerabilities/index.html.haml index d408cdbb6..4d21d6e9d 100644 --- a/app/views/vulnerabilities/index.html.haml +++ b/app/views/vulnerabilities/index.html.haml @@ -3,19 +3,17 @@ page_context[:select_top_menu_nav] = 'select_projects' page_context[:select_footer_nav] = :project_security -%div - = project_analysis_timestamp(@project) - .col-md-10.col-md-offset-2 .row .col-md-1   .col-md-10 .row - %h2= t('.security') + %h2 + %strong= t('.security') .row %h3{ style: 'font-weight: normal; font-size: 1.4em;' }= t('.vulns_per_version') - = link_to 'Learn more about BDSAs', 'https://www.blackduck.com/blog/black-duck-security-advisories-benefits.html', class: 'btn btn-primary learn-more btn-sm pull-right', target: '_blank' + = link_to 'Learn more about BDSAs', 'https://www.blackduck.com/blog/black-duck-security-advisories-benefits.html', class: 'btn btn-primary learn-more btn-md pull-right', target: '_blank', rel: 'noopener noreferrer' .col-md-1   .row diff --git a/app/views/widgets/_widget.html.haml b/app/views/widgets/_widget.html.haml index 68dc95320..f8fa6fe35 100644 --- a/app/views/widgets/_widget.html.haml +++ b/app/views/widgets/_widget.html.haml @@ -13,5 +13,5 @@ .col-md-7.col.pull-right %h6.no_margin_top HTML %p= t('.embed') - %pre.prettyprint + %pre.prettyprint.oh-card = script diff --git a/config/application.rb b/config/application.rb index afaf2992e..728208d1b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -48,7 +48,9 @@ class Application < Rails::Application namespace: ENV.fetch('REDIS_NAMESPACE', nil) } redis_config[:password] = ENV.fetch('REDIS_PASSWORD', nil) unless Rails.env.test? config.cache_store = :redis_store, redis_config - config.action_dispatch.default_headers = { 'X-Content-Type-Options' => 'nosniff' } + config.action_dispatch.default_headers = { + 'X-Content-Type-Options' => 'nosniff' + } config.active_record.dump_schemas = :all Kaminari.configure do |config| diff --git a/config/charting/analysis/options_based_on_type.yml b/config/charting/analysis/options_based_on_type.yml index 46f2b4a25..f6de6294a 100644 --- a/config/charting/analysis/options_based_on_type.yml +++ b/config/charting/analysis/options_based_on_type.yml @@ -24,9 +24,10 @@ commits_history: series: - name: 'Commits' id: 'commits' + color: '#5A2A82' - name: 'Current Month' type: 'scatter' - color: 'rgba(119, 152, 191, 3)' + color: '#5A2A82' marker: radius: 4 symbol: 'circle' @@ -40,12 +41,12 @@ code_history: stacking: 'normal' series: - name: 'Code' - color: '#482268' + color: '#1D0631' id: 'code' - name: 'Comments' - color: '#008893' + color: '#5A2A82' - name: 'Blanks' - color: '#99dde3' + color: '#9F7ABA' tooltip: shared: true @@ -56,9 +57,10 @@ committer_history: series: - name: 'Committers' id: 'committers' + color: '#5A2A82' - name: 'Current Month' type: 'scatter' - color: 'rgba(119, 152, 191, 3)' + color: '#5A2A82' marker: radius: 4 symbol: 'circle' diff --git a/config/charting/combined_commit_history.yml b/config/charting/combined_commit_history.yml index d3cda439b..145a7b44a 100644 --- a/config/charting/combined_commit_history.yml +++ b/config/charting/combined_commit_history.yml @@ -1,9 +1,5 @@ chart: backgroundColor: transparent - style: - background-image: url(<%= chart_background_image('watermark_914.png') %>) - background-repeat: no-repeat - background-position: '50% 50%' plotOptions: series: pointWidth: diff --git a/config/charting/commits_by_project.yml b/config/charting/commits_by_project.yml index 57be2776a..9688570f0 100644 --- a/config/charting/commits_by_project.yml +++ b/config/charting/commits_by_project.yml @@ -4,10 +4,6 @@ plotOptions: chart: marginLeft: 60 backgroundColor: 'transparent' - style: - background-image: url(<%= chart_background_image('watermark_692.png') %>) - background-repeat: 'no-repeat' - background-position: '18% 50%' legend: itemWidth: 170 borderRadius: 0 diff --git a/config/charting/demographic.yml b/config/charting/demographic.yml index 93f389597..afa0d6e83 100644 --- a/config/charting/demographic.yml +++ b/config/charting/demographic.yml @@ -13,25 +13,70 @@ title: fontSize: '10pt' tooltip: formatter: '' - style: - fontSize: '11pt' - padding: '8px' + outside: true + useHTML: true + className: 'demographics-tooltip' + backgroundColor: 'transparent' + borderWidth: 0 + shadow: false + stickOnContact: true + followTouchMove: true plotOptions: pie: allowPointSelect: true cursor: 'pointer' - innerSize: 80 - slicedOffset: 10 - borderColor: '#fff' - borderWidth: 0 + showInLegend: true + innerSize: '55%' + slicedOffset: 0 + borderWidth: 2 dataLabels: enabled: true - minSize: 1 - color: '#000000' - connectorColor: '#000000' - format: '{point.name}: {point.y} %' + distance: 20 + connectorColor: '#94a3b8' + format: '{point.name}: {point.y}%' +legend: + enabled: true + align: 'center' + verticalAlign: 'bottom' + layout: 'horizontal' + symbolRadius: 6 + symbolHeight: 12 + symbolWidth: 12 + labelFormat: '{name} ({y}%)' + itemStyle: + fontWeight: 'normal' + fontSize: '12px' credits: enabled: false series: [{type: 'pie'}] exporting: enabled: false +responsive: + rules: + - condition: + maxWidth: 768 + chartOptions: + plotOptions: + pie: + dataLabels: + enabled: true + distance: 15 + innerSize: '50%' + legend: + itemStyle: + fontSize: '11px' + symbolHeight: 10 + symbolWidth: 10 + - condition: + maxWidth: 640 + chartOptions: + plotOptions: + pie: + dataLabels: + enabled: false + innerSize: '45%' + legend: + itemStyle: + fontSize: '10px' + symbolHeight: 8 + symbolWidth: 8 diff --git a/config/environments/staging.rb b/config/environments/staging.rb index eb0df19b8..cb2b4cb2c 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -93,7 +93,7 @@ logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) - SqlTracker::Config.enabled = ENV['SQL_TRACKER'].eql?('enabled') - SqlTracker::Config.tracked_sql_command = %w[sloc_metrics] + SqlTracker::Config.enabled = true + SqlTracker::Config.tracked_sql_command = %w[SELECT INSERT UPDATE DELETE] SqlTracker::Config.output_path = File.join(ENV['SQL_TRACER_TEMP_PATH'] || '/tmp', 'sql_tracker') end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index e3160c2b9..052e2ce67 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -6,5 +6,6 @@ Rails.application.config.assets.version = '1.0' Rails.application.config.assets.precompile += %w[application.js application.css permissions.js admin/admin.css - api/vulnerability.sass api/vulnerability.js] + api/vulnerability.sass api/vulnerability.js + project_swimlanes.js] Rails.application.config.assets.precompile += %w[*.svg *.eot *.woff *.ttf] diff --git a/config/initializers/ssl_crl_fix.rb b/config/initializers/ssl_crl_fix.rb new file mode 100644 index 000000000..740129544 --- /dev/null +++ b/config/initializers/ssl_crl_fix.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# In development, Zscaler SSL inspection proxy intercepts HTTPS traffic and +# replaces server certificates with its own. Those replacement certs include +# CRL Distribution Point (CDP) extensions whose URLs are unreachable, causing: +# SSL_connect: certificate verify failed (unable to get certificate CRL) +# +# This initializer installs a verify callback that allows CRL-check failures +# through in development while keeping all other certificate checks intact. +if Rails.env.development? + require 'openssl' + + OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.merge!( + verify_callback: proc do |preverify_ok, store_ctx| + if !preverify_ok && store_ctx.error == OpenSSL::X509::V_ERR_UNABLE_TO_GET_CRL + Rails.logger.debug { "[SSL] Skipping CRL check failure for: #{store_ctx.current_cert&.subject}" } + true + else + preverify_ok + end + end + ) +end diff --git a/config/locales/abouts.en.yml b/config/locales/abouts.en.yml index d2e6c7641..d0128307f 100644 --- a/config/locales/abouts.en.yml +++ b/config/locales/abouts.en.yml @@ -12,8 +12,11 @@ en: compare_repositories: 'Compare Repositories' compare_repositories_stats: 'Compare types of repositories based on how many of that type are registered with Open Hub.' language_stats: 'Language Statistics' + language_stats_subtitle: 'Explore programming language metrics across the open source ecosystem' view_stats_per_language: 'View statistics per language, including most experienced contributors, recent contributors, total lines of code analyzed, total lines of comments, and more.' font_size: 'Font size is based on total lines counted per language.' + explore_language_metrics: 'Explore detailed metrics for every language' + view_all_languages: 'View All Languages' view_stats_for_all_languages_html: 'View statistics for %{href}' additional_info: 'Additional Information' api: 'API' diff --git a/config/locales/accounts.en.yml b/config/locales/accounts.en.yml index 7e2b5bcc2..a8be70a69 100644 --- a/config/locales/accounts.en.yml +++ b/config/locales/accounts.en.yml @@ -190,6 +190,7 @@ en: settings: settings_title: '%{name} : Settings - Open Hub' settings: 'Settings' + configure_desc: 'Configure every aspect of the %{name} account on Open Hub.' account_basics: 'Account Basics' password: 'Password' api_keys: 'API Keys' @@ -229,7 +230,7 @@ en: fields: new_account: 'New Account' - sign_up: 'Sign Up' + sign_up: 'Create Account' login_help: 'Your login is your public name on Open Hub and will be associated with all of your contributions.' login: 'Login' email_help_1: 'You must provide a valid email to activate your account.' diff --git a/config/locales/activation_resends.en.yml b/config/locales/activation_resends.en.yml index e992f6250..76d036240 100644 --- a/config/locales/activation_resends.en.yml +++ b/config/locales/activation_resends.en.yml @@ -2,6 +2,9 @@ en: activation_resends: new: heading: 'Activation Resend' + hero_title: 'Activate your' + hero_accent: 'account' + tagline: 'Resend your activation email to complete your Open Hub registration.' fields: enter_email: 'Enter the email you signed up with.' submit: 'Resend my activation email' diff --git a/config/locales/analyses.en.yml b/config/locales/analyses.en.yml index 7b95bc5f3..5788161b9 100644 --- a/config/locales/analyses.en.yml +++ b/config/locales/analyses.en.yml @@ -22,6 +22,7 @@ en: add_new: 'Add a new source code enlistment' language_table: title: 'Language Breakdown' + color: 'Color' language: 'Language' codes: 'Code Lines' comments: 'Comment Lines' diff --git a/config/locales/compares.en.yml b/config/locales/compares.en.yml index c06e78aea..78862b3cc 100644 --- a/config/locales/compares.en.yml +++ b/config/locales/compares.en.yml @@ -39,8 +39,11 @@ en: compare_table_rating: "Open Hub User Rating" project_section: remove_from: "Remove from comparision" + remove_project: "Remove project" enter_name: 'Enter a project name' + project_input_label: "Project %{number}" go: "Go" + add_project: "Add project" project_row: view_as_graph: "View as graph" project_comparison_graph: "Project Comparison Graph" diff --git a/config/locales/en.yml b/config/locales/en.yml index 403d2f936..7a38aa87c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -81,6 +81,10 @@ en: api_exception: We apologize but we encountered an internal error. Please try again later or contact info@openhub.net if the problem persists. kb_message: "Published message for project %{project_id}" + shared: + page_loader: + loading: 'Loading data...' + layouts: application: openhub: "Open Hub" @@ -210,7 +214,7 @@ en: edit_api_key: 'Edit API Key' requests_all_time_view: '%{count}requests all-time' requests_daily_view: '%{count}requests today' - requests_daily_limit_view: 'limited to%{count}requests a day' + requests_daily_limit_view: 'limited to%{count}requests a day' redirect_uri: 'Redirect URI' index: sort_by_most_recent_request: 'Most Recent Request' @@ -375,13 +379,42 @@ en: project_experiences: no_matching_project: No matching open source project with this name. + auth: + shared: + trusted_by: 'Trusted by 890,000+ developers' + testimonial: '"Open Hub helped us assess the health of hundreds of dependencies before our major release."' + testimonial_attr: '— Engineering Lead, Fortune 500' + benefits: + projects: + title: '2.5M+ Open Source Projects' + desc: 'Comprehensive data on millions of repositories worldwide.' + contributors: + title: '890K Contributors' + desc: 'Join a global community of open source developers.' + security: + title: 'Security Intelligence' + desc: 'Track risk scores and license compliance at a glance.' + activity: + title: 'Real-time Activity' + desc: 'Live commit streams, PR activity, and release tracking.' passwords: title: 'Password Reset - Open Hub' new: enter_email_address: Enter the email you signed up with. submit_text: Send me a password reset link + hero_title: 'Reset your' + hero_accent: 'password' + tagline: "Enter your email address and we'll send you instructions to reset your password." + form_title: 'Password Reset' + form_subtitle: 'To be emailed a link to reset your password, please enter your email address.' + email_label: 'Email address' + email_placeholder: 'you@example.com' + submit: 'Reset password' + back_to_sign_in: 'Back to Sign In' + need_help: 'Need help?' + contact_support: 'Contact Support' create: - success: A link to reset your password was sent to your email address. + success: If your email address exists in our database, you will receive a password reset link in a few minutes. reset: success: Your password has been reset successfully. confirm: @@ -398,7 +431,12 @@ en: session_projects: menu: - compare_projects: 'Compare Projects:' + compare_projects: 'Compare Projects (%{count}/3)' + select_project: 'Select Project' + lines: '%{count} lines' + contributors: '%{count} contributors' + compare_n_projects: 'Compare %{count} Projects' + compare_select_at_least: 'Compare (Select at least 2)' limit_reached: limit reached create: limit_exceeded: Project count limit exceeded diff --git a/config/locales/enlistments.en.yml b/config/locales/enlistments.en.yml index 4573c7e27..a2941789c 100644 --- a/config/locales/enlistments.en.yml +++ b/config/locales/enlistments.en.yml @@ -23,7 +23,18 @@ en: allowed_files: 'Allowed Files' actions: 'Actions' new_code_location: 'New Code Location' + show_unprocessable_sources: 'Show unprocessable sources' currently_importing: 'We are currently working on importing your GitHub Repositories. This might take some time. Please refresh the page or check back momentarily.' + file: 'file' + description1: 'Open Hub ignored' + description2: 'during processing.' + description3: 'All files included.' + description4: 'Open Hub allowed' + edit_ignored: 'Edit ignored files' + edit_allowed: 'Edit allowed files' + remove: 'Remove' + confirm: 'Are you sure you want to delete this code location?' + dnf: "Error: This Code Location is flagged as Do Not Fetch. Please contact us for assistance" new: page_title: 'The %{name} Open Source Project on Open Hub : New Code Location Page' code_locations: 'Code Locations' diff --git a/config/locales/explore.en.yml b/config/locales/explore.en.yml index 39fdd6b03..8a980b582 100644 --- a/config/locales/explore.en.yml +++ b/config/locales/explore.en.yml @@ -12,6 +12,7 @@ en: title: 'Orgs by 30 Day Commit Volume' no_results: 'No Results found matching the filter.' filter: 'Filter by: ' + filter_type_label: 'Filter by organization type' org: 'Organization' type: 'Type' size: 'Size' diff --git a/config/locales/home.en.yml b/config/locales/home.en.yml index 9e5497ce0..fab5a22e4 100644 --- a/config/locales/home.en.yml +++ b/config/locales/home.en.yml @@ -3,24 +3,89 @@ en: index: page_title: 'Open Hub, the open source network' discover_message: 'Discover, Track and Compare Open Source' + worlds_largest_directory: "The world's largest directory of open source projects and contributors" join_now: 'Join Now' - whats_new: "What's New" counting: 'Counting %{count} lines of code' indexing: 'Indexing %{count} open source projects' connecting: 'Connecting %{count} open source contributors' tracking: 'Tracking %{count} source control repositories' search_projects: 'Search Projects...' + feature_directory_title: 'Massive Directory' + feature_directory_desc: 'Indexing over 2.5 million open source projects.' + feature_health_title: 'Assess Health' + feature_health_desc: 'Check activity levels and risk before you adopt.' + feature_portfolio_title: 'Manage Portfolio' + feature_portfolio_desc: 'Claim your commits and track your developer rankings.' + projects_label: 'Projects' + contributors_label: 'Contributors' + languages_label: 'Languages' + organizations_label: 'Organizations' top_lists: popular_projects: 'Most Popular Projects' active_projects: 'Most Active Projects' active_contributors: 'Most Active Contributors' recent_projects: 'Recently Added Projects' + show_more: 'Show More' + show_less: 'Show Less' + view_all_projects: 'Explore Projects' join_now_home: - claim: 'Claim' - contributions: 'your contributions' - manage: 'Manage' - projects_data: "your project's data" - highlight: 'Highlight' - use: 'your use of FOSS' join_now: 'Join Now' + become_part_community: 'Become part of the community' + track_contributions_title: 'Track Your Contributions' + track_contributions_desc: 'Showcase your open source portfolio and contributions' + gain_recognition_title: 'Gain Recognition' + gain_recognition_desc: 'Build your developer profile with verified achievements' + create_account: 'Create Your Account' + + whats_new: + whats_new: "What's New" + latest_updates_reports: 'Latest updates and reports' + ossra_report_alt: 'Open Source Security and Risk Analysis Report' + + top_contributors: + top_contributors: 'Top Contributors' + most_active_developers: 'Most active contributors' + view_all_contributors: 'View All Contributors' + view_all: 'View all' + + user_journeys: + your_journey_with_openhub: 'Your Journey with Open Hub' + explore_ways_description: 'Explore the many ways you can discover, track, and contribute to open source projects' + key_steps: 'Key Steps' + + discover_compare_title: 'Discover & Compare Solutions' + discover_compare_desc: 'Find the perfect open source solution by comparing features, project activity, and community engagement side-by-side.' + discover_compare_step1: 'Compare projects side-by-side' + discover_compare_step2: 'Review activity metrics' + discover_compare_step3: 'Analyze community interest' + + assess_health_title: 'Assess Project Health' + assess_health_desc: 'Determine if a project is safe to adopt by analyzing commit frequency, number of contributors, and growth trends.' + assess_health_step1: 'Analyze commit history trends' + assess_health_step2: 'Review year-over-year growth' + assess_health_step3: 'Track number of contributors' + + manage_portfolio_title: 'Manage Your Developer Portfolio' + manage_portfolio_desc: 'Build a unified portfolio of your open source work by claiming your contributions across all repositories.' + manage_portfolio_step1: 'Claim your commits' + manage_portfolio_step2: 'Earn "Kudos" and rankings' + manage_portfolio_step3: 'Track your global rank' + + analyze_code_title: 'Analyze Code Composition' + analyze_code_desc: 'Get deep insights into the codebase architecture, including language breakdown, lines of code, and comment ratios.' + analyze_code_step1: 'View language distribution' + analyze_code_step2: 'Check comment-to-code ratios' + analyze_code_step3: 'Estimate effort (COCOMO model)' + + check_security_title: 'Check Security Vulnerabilities' + check_security_desc: 'Verify the security track record of a project by checking known vulnerabilities and confidence scores before you adopt.' + check_security_step1: 'View Security Confidence Index' + check_security_step2: 'Check reported vulnerabilities (CVEs)' + check_security_step3: 'Assess risk level' + + identify_opportunities_title: 'Identify Contribution Opportunities' + identify_opportunities_desc: 'Find projects that need your skills and identify the best repositories to start your open source journey.' + identify_opportunities_step1: 'Find beginner-friendly projects' + identify_opportunities_step2: 'Locate source repositories (GitHub/GitLab)' + identify_opportunities_step3: 'Track your contribution impact' diff --git a/config/locales/kudos.en.yml b/config/locales/kudos.en.yml index f4a1f59f6..7cd64dd8e 100644 --- a/config/locales/kudos.en.yml +++ b/config/locales/kudos.en.yml @@ -20,7 +20,9 @@ en: aka: "aka" new: include_message: "Include an optional message with your kudos to %{who}" + message_placeholder: "Write a kind message... (optional)" maximum: "Maximum 80 characters." + show_appreciation: "Show your appreciation" submit: "Give Kudos" create: success_account: "You have given kudos to %{name}." diff --git a/config/locales/languages.en.yml b/config/locales/languages.en.yml index 4132fc52d..1ed1e7b14 100644 --- a/config/locales/languages.en.yml +++ b/config/locales/languages.en.yml @@ -21,6 +21,7 @@ en: more: 'More' tools: 'Tools' measure_heading: "%{measure} (Percent of Total)" + select_language: 'Select language' update: 'Update' show: title: "%{title} Programming Language Statistics - Open Hub" diff --git a/config/locales/organizations.en.yml b/config/locales/organizations.en.yml index 3391ffe7d..81392c3ef 100644 --- a/config/locales/organizations.en.yml +++ b/config/locales/organizations.en.yml @@ -22,6 +22,12 @@ en: rating: 'Community Rating' reviews: 'Reviews' description: 'Description' + logo: 'Logo' + activity: 'Activity' + actions: 'Actions' + organization: + projects: 'Projects' + affiliated_committers: 'Affiliated Committers' index: no_matches: 'No matches with this filter.' title: 'Organizations' @@ -116,6 +122,8 @@ en: portfolio_projects: 'Portfolio Projects' header: settings: 'Settings' + portfolio_project: 'portfolio project' + affiliated_committer: 'affiliated committer' quick_reference: title: 'Quick Reference' org_type: 'Organization Type:' @@ -136,7 +144,10 @@ en: commits: 'Commits' affiliated: 'Affiliated' outside: 'Outside' + not_available: 'N/A' + none: 'none' see_all_projects: 'See All %{num} Portfolio Projects' + activity: 'Activity' outside_projects: outside_projects: 'Outside Projects' no_outside_projects: "There are no outside projects to display." @@ -146,6 +157,7 @@ en: header_community_rating: "Community Rating" header_num_of_affiliates: "# of Affiliates Contributing" header_all_time_commits: "All-time Commits" + header_activity: "Activity" header_by_current_affiliates: "(by Current Affiliates)" see_all_num_outside_projs: "See All %{num} Outside Projects" outside_committers: @@ -176,8 +188,11 @@ en: num_commits: "#Commits" date: "Date" you: "(You)" + commit: "commit" see_all: "See All %{num} Affiliated Contributors" settings: + title: 'Settings' + configure_desc: 'Configure every aspect of the %{name} organization on Open Hub.' basics: Organization Basics name_description_and_url: Name, description and Open Hub URL for Organization. users_who_manage: Users who manage the organization settings and oversee the organization on Open Hub. diff --git a/config/locales/passwords.en.yml b/config/locales/passwords.en.yml index 558ea0777..e38a53226 100644 --- a/config/locales/passwords.en.yml +++ b/config/locales/passwords.en.yml @@ -1,4 +1,6 @@ en: + password_resets: + already_logged_in: 'You are already logged in.' activerecord: errors: models: diff --git a/config/locales/projects.en.yml b/config/locales/projects.en.yml index 723000732..5a3f67d10 100644 --- a/config/locales/projects.en.yml +++ b/config/locales/projects.en.yml @@ -49,22 +49,27 @@ en: claimed_by: "Claimed by" analyzed_ago: "Analyzed %{time} ago" no_analysis_available: "No analysis available" + no_description_available: "No description available" + mostly_written_in: "Mostly written in %{lang}" + not_available: "Not available" + licenses: "Licenses:" + no_declared_licenses: "No declared licenses" + tags: "Tags" + n_more: "%{n} more..." loc: "lines of code" current_contributors: "current contributors" since_last_commit: "since last commit" users_on_open_hub: "users on Open Hub" review: "Review" - mostly_written_in: "Mostly written in %{lang}" + i_use_this: "I Use This" mostly_written_lang_not_available: "Mostly written in language not available" - licenses: "Licenses:" - no_declared_licenses: "No declared licenses" - n_more: "%{n} more..." index_managed: page_title: "%{name} : Projects - Open Hub" managed_projects: "Managed Projects" settings: page_title: 'Settings Page' title: 'Settings' + configure_desc: 'Configure every aspect of the %{name} project on Open Hub.' project_basis: 'Project Basics' project_basis_desc: 'Project name, description, and Open Hub URL for the project.' code_location: 'Code Locations' @@ -92,6 +97,16 @@ en: show: page_title: "The %{name} Open Source Project on Open Hub" project_summary: "Project Summary" + overview: "Overview" + news: "News" + settings: "Settings" + sharing_widgets: "Sharing Widgets" + related_projects: "Related Projects" + languages: "Languages" + commits: "Commits" + contributors: "Contributors" + users: "Users" + security: "Security" no_description: "No description has been added for this project." add_description: "Add description" tags: "Tags" @@ -99,20 +114,57 @@ en: add_tags: "add some now" community_rating: "Community Rating" in_a_nutshell: "In a Nutshell, %{name}" + lines_of_code: "Lines of Code" + contributors: "Contributors" + total_commits: "Total Commits" + note: "Note:" + organization: "Organization" + homepage: "Homepage" + documentation: "Documentation" + code_location: "Code Location" + add_code_location_link: "Add code location" + similar_projects_label: "Similar Projects" + managers_label: "Managers" + become_first_manager: "Become the first manager for %{what}" + news: "News" + sharing_widgets: "Sharing Widgets" + related_projects: "Related Projects" + code_data: "Code Data" + languages_label: "Languages" + cost_estimates: "Cost Estimates" + security_label: "Security" + scm_data: "SCM Data" + commits_label: "Commits" + contributors_label: "Contributors" + community_data: "Community Data" + users_label: "Users" + ratings_reviews: "Ratings & Reviews" + user_contributor_locations: "User & Contributor Locations" + licenses_title: "Licenses" site_features: did_you_know: 'Did You Know' analysis_summary: code_locations_removed: "Code locations have been removed since last analysis" code_locations_removed_explanation: "At one point, Open Hub analyzed source code for this project based on code location(s) available at that time. Since then, the code locations have been removed." + code: "Code" + lines_of_code: "Lines of Code" + languages: "Languages" activity: "Activity" - code: 'Code' - thirty_day_summary: "30 Day Summary" - not_available: "not available" - twelve_month_summary: "12 Month Summary" commits_per_month: "Commits per Month" add_a_code_location: "Add a code location" - no_code_locations: "There are no Code Locations added to this project so Open Hub cannot display the commits per month chart. %{link} to see this information." + no_code_locations: "There are no Code Locations added to this project so Open Hub cannot display this chart." + zoom: "Zoom" + no_commits: "No commit data available" + thirty_day_summary: "30 Day Summary" + twelve_month_summary: "12 Month Summary" + not_available: "not available" + commits: "Commits" + contributors: "Contributors" community: "Community" + contributors_per_month: "Contributors per Month" + no_contributors: "No contributor data available" + most_recent_contributors: "Most Recent Contributors" + ratings: "Ratings" committers_per_month_chart: contributors_per_month: "Contributors per Month" add_a_code_location: "Add a code location" @@ -125,14 +177,34 @@ en: be_the_first: "Be the first to rate this project" click_to_edit: "Click to edit your rating" click_to_add: "Click to add your rating" + click_to_add_review: "Click to add your review" clear: "Clear" - review_this_project: "Review this Project!" + review_this_project: "Review this project" community_recent_committers: most_recent: "Most Recent Contributors" no_one_recently: "No one has contributed to this project recently" + no_contributors: "No contributor data available" + header_redesign: + i_use_this: 'I Use This!' + you_use_this: 'You use this' + login_required: 'Login Required' + settings: 'Settings' + edit_duplicate: 'Edit Duplicate' + report_duplicate: 'Report Duplicate' + users: 'users' + analyzed: 'Analyzed %{time} ago.' + based_on_code: 'Based on code collected %{time} ago.' + no_description: 'No description available' header: i_use_this: 'I Use This!' + you_use_this: 'You use this' login_required: 'Login Required' + settings: 'Settings' + edit_duplicate: 'Edit Duplicate' + report_duplicate: 'Report Duplicate' + users: 'users' + analyzed: 'Analyzed %{time} ago.' + based_on_code: 'Based on code collected %{time} ago.' i_use_this: i_use_this: 'I Use This!' is_a_duplicate: @@ -143,6 +215,7 @@ en: languages: no_source: "No source code was found in any of the code locations" languages: "Languages" + pie_chart_alt: "Language distribution pie chart for %{project}" add_a_code_location: "Add a code location" no_code_locations: "There are no Code Locations added to this project so Open Hub cannot display the language breakdown. %{link} to see this information." lines_of_code_chart: @@ -179,7 +252,7 @@ en: vulnerability_report: no_data: 'No data available' pvs_tooltip: 'Score: %{score}
Percentage: %{percentage}
Rank: %{rank} of %{max_rank}' - title: 'Project Vulnerability Report' + title: 'Vulnerability Report' blog_link: 'About Project Vulnerability Report' pvs: title: 'Vulnerability Exposure Index' @@ -191,7 +264,7 @@ en: poor: 'Poor security track-record' vulnerabilities_per_version: title: 'Vulnerabilities per Version' - title_detail: '( last 10 releases )' + title_detail: '(Most recent versions)' no_data: 'No data available' thirty_day_summary: commit: "Commit" @@ -221,11 +294,17 @@ en: most_recent_commit: most recent commit %{time} security: title: 'Project Security' - title_nvd: 'This Project has No vulnerabilities Reported Against it' + title_nvd: 'This project has no vulnerabilities reported against it' blog_link: 'About Project Security' licenses: title: 'Licenses' all: 'All Licenses' + licenses: 'Licenses' + permitted: 'Permitted' + forbidden: 'Forbidden' + required: 'Required' + disclaimer: 'These details are provided for information only. No information here is legal advice and should not be used as such.' + view_all_licenses: 'View All Licenses' license_details: info: 'These details are provided for information only. No information here is legal advice and should not be used as such.' deleted: @@ -307,6 +386,8 @@ en: contact_us: "Contact us" project_name_help: "This name must be unique across all projects on Open Hub.
%{link} if you believe someone is using the name of your project inappropriately on Open Hub." project_source_code: "Project Source Code" + repository_url: "Repository URL" + branch: "Branch" i_manage_this_project: "I manage this project" i_manage_this_project_help1: "Click to manage this project on Open Hub. You should already contribute to the project in a significant way." i_manage_this_project_help2: "Open Hub project managers will normally be the project's leader or someone they designate." diff --git a/config/locales/rss_subscriptions.en.yml b/config/locales/rss_subscriptions.en.yml index 1a9374e46..314d77e72 100644 --- a/config/locales/rss_subscriptions.en.yml +++ b/config/locales/rss_subscriptions.en.yml @@ -12,6 +12,7 @@ en: confirm_text: 'Are you sure you want to delete this RSS feed?' page_title: "The %{name} Open Source Project on Open Hub : News Feeds Page" remove: 'Remove' + actions: 'Actions' no_feed_exists: 'No news feeds exists' explain: 'Subscribed to RSS feed %{url}' new: diff --git a/config/locales/sessions.en.yml b/config/locales/sessions.en.yml index 3b50062bc..601091ab7 100644 --- a/config/locales/sessions.en.yml +++ b/config/locales/sessions.en.yml @@ -12,6 +12,18 @@ en: forgot_password: "Forgot Password" resend_activation_email: "Resend Activation Email" sign_in_with: Sign in with + hero_welcome: 'Welcome' + hero_back_to: 'back to' + hero_accent: 'Open Hub' + tagline: "The world's largest directory of open source projects and contributors." + divider: 'or sign in with email' + form_title: 'Sign in to your account' + no_account: "Don't have an account?" + join_now: "Join Now — it's free" + sign_in_with_email: 'Sign in with Email & Password' + terms_preamble: 'By signing in you agree to the' + terms_of_service: 'Terms of Service' + privacy_policy: 'Privacy Policy' sign_in: log_in_to_open_hub: "Log in to Open Hub" login_or_email: "Login or Email" diff --git a/config/locales/shared.en.yml b/config/locales/shared.en.yml index c46cc5689..ee7593456 100644 --- a/config/locales/shared.en.yml +++ b/config/locales/shared.en.yml @@ -26,6 +26,8 @@ en: search_bar: filter_by_tags: 'Filter by Related Tags:' search_text: 'Search / Filter on:' + clear_search: 'Clear search' + search: 'Search' no_match_found: no_match: 'No Matches with this filter' no_match_1_html: "Your search - %{query} - did not match anything." @@ -36,11 +38,18 @@ en: no_match_6: 'Try fewer keywords.' search: search_text: 'Search...' + search: 'Search' analysis_timestamp: ago: 'ago.' analyzed: 'Analyzed' based_on: 'based on code collected' project_may_be_current: 'project data may be more current' + global_search: + projects: 'Projects' + people: 'People' + organizations: 'Organizations' + placeholder: 'Search...' + search: 'Search' api_outage: heading: Under Maintenance description_1: This feature is currently under maintenance. diff --git a/config/routes.rb b/config/routes.rb index 3bab060fd..e33276330 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -98,6 +98,8 @@ get :confirm_delete get :disabled get :settings + get :theme_preference, defaults: { format: :json } + post :set_theme_preference get 'alter_password/edit', to: 'alter_passwords#edit' patch 'alter_password/edit', to: 'alter_passwords#update' get :edit_privacy, to: 'privacy#edit', as: :edit_account_privacy diff --git a/config/shared/sort_options.yml b/config/shared/sort_options.yml index def90df4e..3ad81dbc4 100644 --- a/config/shared/sort_options.yml +++ b/config/shared/sort_options.yml @@ -181,6 +181,35 @@ organizations: default: projects +org_types: + options: + - + - All Organizations + - all_orgs + - + - Commercial + - commercial + - + - Education + - educational + - + - Government + - government + - + - Non-Profit + - non_profit + - + - Large + - large + - + - Medium + - medium + - + - Small + - small + default: + all_orgs + claim_projects_list: options: - diff --git a/spec/javascripts/fixtures/enlistments.html.haml b/spec/javascripts/fixtures/enlistments.html.haml index 314edfe74..29c22f3f1 100644 --- a/spec/javascripts/fixtures/enlistments.html.haml +++ b/spec/javascripts/fixtures/enlistments.html.haml @@ -6,7 +6,7 @@ %option{ value: 'BzrRepository' } Bazaar %option{ value: 'GithubUser' } GithubRepositories -.well.enlistment +.oh-card.enlistment .default-url-tags %input{ type: 'text', name: 'url' } diff --git a/test/controllers/accounts_controller_test.rb b/test/controllers/accounts_controller_test.rb index 3c65c5290..3d2aab105 100644 --- a/test/controllers/accounts_controller_test.rb +++ b/test/controllers/accounts_controller_test.rb @@ -317,7 +317,7 @@ class AccountsControllerTest < ActionController::TestCase text = 'about raw content' post :update, params: { id: account.to_param, account: { email: '', about_raw: text } } - assert_select 'textarea.edit-description', text: text + assert_select 'textarea.edit-field-textarea', text: text end it 'must not allow description beyond 500 characters' do @@ -556,4 +556,91 @@ class AccountsControllerTest < ActionController::TestCase end end end + + describe 'theme_preference' do + let(:theme_account) { create(:account) } + + describe 'GET theme_preference' do + it 'must return unauthorized if not authenticated' do + get :theme_preference, params: { id: theme_account.id }, format: :json + + assert_response :unauthorized + end + + it 'must return theme preference as JSON when authenticated' do + login_as theme_account + theme_account.theme_preference = 'dark' + + get :theme_preference, params: { id: theme_account.id }, format: :json + + assert_response :success + response_body = JSON.parse(@response.body) + _(response_body['theme_preference']).must_equal('dark') + end + + it 'must return nil as default theme preference (sparse storage)' do + login_as theme_account + + get :theme_preference, params: { id: theme_account.id }, format: :json + + assert_response :success + response_body = JSON.parse(@response.body) + _(response_body['theme_preference']).must_be_nil + end + end + + describe 'POST set_theme_preference' do + it 'must redirect to login if not authenticated' do + post :set_theme_preference, params: { id: theme_account.id, theme: 'dark' } + + assert_redirected_to new_session_path + end + + it 'must save dark theme preference when authenticated' do + login_as theme_account + + post :set_theme_preference, params: { id: theme_account.id, theme: 'dark' } + + assert_response :success + response_body = JSON.parse(@response.body) + _(response_body['success']).must_equal(true) + _(response_body['theme']).must_equal('dark') + + _(theme_account.reload.theme_preference).must_equal('dark') + end + + it 'must delete database entry when setting to light (sparse storage)' do + login_as theme_account + theme_account.theme_preference = 'dark' + + post :set_theme_preference, params: { id: theme_account.id, theme: 'light' } + + assert_response :success + response_body = JSON.parse(@response.body) + _(response_body['success']).must_equal(true) + + _(theme_account.reload.theme_preference).must_be_nil + _(Setting.find_by(key: "account_#{theme_account.id}_theme_preference")).must_be_nil + end + + it 'must delete entry when setting to light (falls back to OS preference)' do + login_as theme_account + theme_account.theme_preference = 'dark' + + post :set_theme_preference, params: { id: theme_account.id, theme: 'light' } + + assert_response :success + _(theme_account.reload.theme_preference).must_be_nil + end + + it 'must return success response with JSON format' do + login_as theme_account + + post :set_theme_preference, params: { id: theme_account.id, theme: 'dark' } + + assert_response :success + assert_equal('application/json', response.media_type) + end + end + end end diff --git a/test/controllers/commits_controller_test.rb b/test/controllers/commits_controller_test.rb index 63585db36..d66df6e79 100644 --- a/test/controllers/commits_controller_test.rb +++ b/test/controllers/commits_controller_test.rb @@ -157,6 +157,11 @@ class CommitsControllerTest < ActionController::TestCase _(assigns(:commits).first).must_equal @commit1 end + it 'should populate commit_contributors' do + get :summary, params: { project_id: @project.id } + _(assigns(:commit_contributors)).wont_be_nil + end + it 'should show no code_location when enlistment is empty' do Project.any_instance.stubs(:best_analysis).returns(NilAnalysis.new) get :summary, params: { project_id: @project.id } @@ -186,6 +191,19 @@ class CommitsControllerTest < ActionController::TestCase _(assigns(:daily_commits).first.time.to_a).must_equal @commit1.time.to_a _(assigns(:daily_commits).first.comment).must_equal @commit1.comment end + + it 'should render no analysis error when analysis is empty' do + Project.any_instance.stubs(:best_analysis).returns(NilAnalysis.new) + get :events, params: { project_id: @project.id, id: @commit1.id, contributor_id: @name1.id }, format: :xml + assert_response :not_found + end + + it 'should render 404 when contributor_fact is not found' do + create(:name_fact, analysis: @project.best_analysis, name: @name1) + Airbrake.stubs(:notify) + get :events, params: { project_id: @project.id, id: @commit1.id, contributor_id: 999_999 }, format: :xml + assert_response :not_found + end end describe 'event_details' do @@ -196,6 +214,32 @@ class CommitsControllerTest < ActionController::TestCase _(assigns(:commits).count).must_equal 1 _(assigns(:commits).first).must_equal @commit1 end + + it 'should handle plain time param without commit_ prefix' do + create(:name_fact, analysis: @project.best_analysis, name: @name1) + get :event_details, params: { contributor_id: @name1.id, id: @commit1.id, + project_id: @project.id, time: @commit1.time.to_i.to_s } + assert_response :ok + end + end + + describe 'redirect_to_message_if_oversized_project' do + it 'should redirect to root for oversized projects' do + CommitsController.any_instance.stubs(:oversized_project?).returns(true) + get :index, params: { project_id: @project.id } + assert_redirected_to root_path + _(flash[:notice]).must_equal I18n.t('commits.project_temporarily_disabled') + end + end + + describe 'individual_named_commits' do + it 'should return empty commits when commit_contributor is nil' do + Project.any_instance.stubs(:commit_contributors).returns(stub(find_by: nil)) + contribution = @project.contributions.first + get :index, params: { project_id: @project.id, contributor_id: contribution.id } + assert_response :ok + _(assigns(:commits)).must_equal [] + end end private diff --git a/test/controllers/explore_controller_test.rb b/test/controllers/explore_controller_test.rb index 4b6c52be3..fc2922499 100644 --- a/test/controllers/explore_controller_test.rb +++ b/test/controllers/explore_controller_test.rb @@ -39,7 +39,7 @@ class ExploreControllerTest < ActionController::TestCase describe 'orgs_by_thirty_day_commit_volume' do it 'should return json of filtered record when filter is all_orgs' do - get :orgs_by_thirty_day_commit_volume, params: { filter: 'all_orgs' }, format: :js, xhr: true + get :orgs_by_thirty_day_commit_volume, params: { org_type: 'all_orgs' }, format: :js, xhr: true assert_response :ok _(assigns(:org_by_30_day_commits)).must_equal [ota5, ota4, ota3, ota2, ota1] @@ -47,14 +47,14 @@ class ExploreControllerTest < ActionController::TestCase it 'should return json of filtered record when filter is government' do OrgThirtyDayActivity.where(id: [ota5.id, ota4.id, ota3.id]).update_all(org_type: 3) - get :orgs_by_thirty_day_commit_volume, params: { filter: 'government' }, format: 'js', xhr: true + get :orgs_by_thirty_day_commit_volume, params: { org_type: 'government' }, format: 'js', xhr: true assert_response :ok _(assigns(:org_by_30_day_commits)).must_equal [ota5, ota4, ota3] end it 'should return json of filtered record when filter is all' do - get :orgs_by_thirty_day_commit_volume, params: { filter: 'all_orgs' }, format: :js, xhr: true + get :orgs_by_thirty_day_commit_volume, params: { org_type: 'all_orgs' }, format: :js, xhr: true assert_response :ok _(assigns(:org_by_30_day_commits)).must_equal [ota5, ota4, ota3, ota2, ota1] diff --git a/test/controllers/home_controller_test.rb b/test/controllers/home_controller_test.rb index 04d2523cc..3e27b7716 100644 --- a/test/controllers/home_controller_test.rb +++ b/test/controllers/home_controller_test.rb @@ -9,7 +9,13 @@ class HomeControllerTest < ActionController::TestCase best_account_analysis.account.update(best_vita_id: best_account_analysis.id, created_at: 4.days.ago) account_analysis_fact = best_account_analysis.account_analysis_fact account_analysis_fact.update(last_checkin: Time.current) - Rails.cache.stubs(:fetch).returns(Account.recently_active) + Rails.cache.stubs(:fetch).with('HomeDecorator-recently_active_accounts-cache').returns(Account.recently_active) + Rails.cache.stubs(:fetch).with('HomeDecorator-person_count-cache').returns(1_000_000) + Rails.cache.stubs(:fetch).with('HomeDecorator-active_project_count-cache').returns(2_500_000) + Rails.cache.stubs(:fetch).with('HomeDecorator-repository_count-cache').returns(500_000) + Rails.cache.stubs(:fetch).with('HomeDecorator-vita_count-cache').returns([]) + Rails.cache.stubs(:fetch).with('HomeDecorator-most_active_projects-cache').returns([]) + Rails.cache.stubs(:fetch).with('homepage_top_lists').returns([]) get :index assert_response :success diff --git a/test/controllers/licenses_controller_test.rb b/test/controllers/licenses_controller_test.rb index b2ac3ce27..46d3914d4 100644 --- a/test/controllers/licenses_controller_test.rb +++ b/test/controllers/licenses_controller_test.rb @@ -52,7 +52,7 @@ class LicensesControllerTest < ActionController::TestCase get :show, params: { id: @license.vanity_url } - _(assert_select('p')[3].text).must_equal "foo \n " + _(assert_select('.license-description p').first.text).must_equal "foo \n " end end diff --git a/test/controllers/links_controller_test.rb b/test/controllers/links_controller_test.rb index 0c1b44e29..2bf29c3d1 100755 --- a/test/controllers/links_controller_test.rb +++ b/test/controllers/links_controller_test.rb @@ -50,7 +50,7 @@ class LinksControllerTest < ActionController::TestCase get :index, params: { project_id: project.vanity_url } - assert_select '.alert', text: '× You can view, but not change this data. Only managers may change this data.' + assert_select '.alert', text: 'You can view, but not change this data. Only managers may change this data.' end it 'index must display sanitized links' do diff --git a/test/controllers/managers_controller_test.rb b/test/controllers/managers_controller_test.rb index af020a294..7fda30cb3 100644 --- a/test/controllers/managers_controller_test.rb +++ b/test/controllers/managers_controller_test.rb @@ -25,7 +25,7 @@ class ManagersControllerTest < ActionController::TestCase get :index, params: { project_id: @proj.to_param } assert_response :success assert_select 'a.btn.btn-primary' - assert_select '.col-md-4 a.btn.btn-primary', text: 'I manage this project on Open Hub' + assert_select '.col-md-8 a.btn.btn-primary', text: 'I manage this project on Open Hub' end it 'must render projects/deleted when project is deleted' do @@ -47,7 +47,7 @@ class ManagersControllerTest < ActionController::TestCase a.save get :index, params: { project_id: @proj.to_param } assert_response :success - assert_select 'p a.btn.btn-small.btn-danger', text: /remove manager/ + assert_select 'p a.btn.btn-md.btn-danger', text: /remove manager/ end it 'test index for applicant' do diff --git a/test/controllers/organizations_controller_test.rb b/test/controllers/organizations_controller_test.rb index e59b18bb3..d75f78bde 100644 --- a/test/controllers/organizations_controller_test.rb +++ b/test/controllers/organizations_controller_test.rb @@ -83,7 +83,7 @@ class OrganizationsControllerTest < ActionController::TestCase get :show, params: { id: @organization.vanity_url } - _(assert_select('p')[3].text).must_equal "foo \n " + _(assert_select('#org_summary p').first.text).must_equal "foo \n " end it 'should support show page via xml api' do diff --git a/test/controllers/project_licenses_controller_test.rb b/test/controllers/project_licenses_controller_test.rb index 22c8fdf7a..2a441b9ac 100644 --- a/test/controllers/project_licenses_controller_test.rb +++ b/test/controllers/project_licenses_controller_test.rb @@ -10,7 +10,7 @@ class ProjectLicensesControllerTest < ActionController::TestCase get :index, params: { project_id: project.to_param } assert_response :ok assert_select '#flash-msg .alert', 1 - assert_select 'tr.license', 0 + assert_select '.license-list-item', 0 assert_select 'a.new-license', 1 assert_select 'a.new-license.needs_login', 1 assert_select 'a.new-license.disabled', 0 @@ -26,7 +26,7 @@ class ProjectLicensesControllerTest < ActionController::TestCase assert_response :ok _(flash[:notice]).must_equal I18n.t('permissions.must_log_in') assert_select '#flash-msg .alert', 1 - assert_select 'tr.license', 3 + assert_select '.license-list-item', 3 assert_select 'a.new-license', 1 assert_select 'a.new-license.needs_login', 1 assert_select 'a.new-license.disabled', 0 @@ -80,7 +80,7 @@ class ProjectLicensesControllerTest < ActionController::TestCase login_as create(:account) get :new, params: { project_id: project.to_param } assert_response :ok - assert_select 'input.add-license', 1 + assert_select 'button.btn-add-license', 1 assert_select 'a.add-license.needs_login', 0 assert_select 'a.add-license.disabled', 0 end diff --git a/test/controllers/project_tags_controller_test.rb b/test/controllers/project_tags_controller_test.rb index 50e02cffe..2bb1d2ca1 100644 --- a/test/controllers/project_tags_controller_test.rb +++ b/test/controllers/project_tags_controller_test.rb @@ -23,7 +23,7 @@ class ProjectTagsControllerTest < ActionController::TestCase project = create(:project) get :index, params: { project_id: project.to_param } assert_response :success - assert_select '.alert', 2 + assert_select '.alert', 1 _(response.body).must_match I18n.t('project_tags.index.no_other_projects') end end diff --git a/test/controllers/projects_controller_test.rb b/test/controllers/projects_controller_test.rb index def7e2c23..c9de90f8f 100644 --- a/test/controllers/projects_controller_test.rb +++ b/test/controllers/projects_controller_test.rb @@ -20,9 +20,9 @@ class ProjectsControllerTest < ActionController::TestCase login_as nil get :index, params: { query: 'foo' } assert_response :ok - assert_select "div.well#project_#{project1.id}", true - assert_select "div.well#project_#{project2.id}", true - assert_select "div.well#project_#{project3.id}", false + assert_select "div#project_#{project1.id}", true + assert_select "div#project_#{project2.id}", true + assert_select "div#project_#{project3.id}", false end it 'index should handle the q param for unlogged users' do @@ -32,9 +32,9 @@ class ProjectsControllerTest < ActionController::TestCase login_as nil get :index, params: { q: 'foo' } assert_response :ok - assert_select "div.well#project_#{project1.id}", true - assert_select "div.well#project_#{project2.id}", true - assert_select "div.well#project_#{project3.id}", false + assert_select "div#project_#{project1.id}", true + assert_select "div#project_#{project2.id}", true + assert_select "div#project_#{project3.id}", false end it 'index should handle query param that matches no project' do @@ -292,7 +292,7 @@ class ProjectsControllerTest < ActionController::TestCase get :show, params: { id: project.vanity_url } - _(assert_select('p')[3].text).must_equal "foo \n " + _(assert_select('p')[1].text).must_equal "foo \n " end it 'show accepts being called via api' do diff --git a/test/controllers/session_projects_controller_test.rb b/test/controllers/session_projects_controller_test.rb index 3391bca51..f7fb07734 100644 --- a/test/controllers/session_projects_controller_test.rb +++ b/test/controllers/session_projects_controller_test.rb @@ -28,7 +28,7 @@ class SessionProjectsControllerTest < ActionController::TestCase assert_response :success _(session[:session_projects]).must_equal [project.to_param] - assert_select "form#sp_frm_#{project.to_param}" + assert_select "button.remove-project[data-project-id='#{project.to_param}']" end it 'must prevent bot access' do diff --git a/test/decorators/baseball_card_test.rb b/test/decorators/baseball_card_test.rb index d10d26f1a..59e53946c 100644 --- a/test/decorators/baseball_card_test.rb +++ b/test/decorators/baseball_card_test.rb @@ -28,18 +28,18 @@ class BaseballCardTest < ActiveSupport::TestCase result = [{ css: {}, label: 'First commit', value: first_commit_day }, { css: {}, label: 'Most recent commit', value: last_commit_day }, - { css: {}, label: 'Has made', value: commits }, + { css: { class: 'statistic--commits' }, label: 'Has made', value: commits }, { css: {}, label: 'Joined Open Hub', value: joined_day }, { css: {}, label: 'Contributed to', value: "1 project" }, { - css: { style: 'min-height:38px;' }, + css: { class: 'statistic--orgs', style: 'min-height:38px;' }, label: I18n.t('accounts.show.baseball_card.contributed_to'), partial: 'accounts/show/orgs', locals: { orgs: [org] } }, { - css: { style: 'min-height:38px;' }, + css: { class: 'statistic--orgs', style: 'min-height:38px;' }, label: I18n.t('accounts.show.baseball_card.contributed_for'), partial: 'accounts/show/orgs', locals: { orgs: [org] } diff --git a/test/decorators/icon_test.rb b/test/decorators/icon_test.rb index 59882f2ae..9cf340527 100644 --- a/test/decorators/icon_test.rb +++ b/test/decorators/icon_test.rb @@ -8,17 +8,30 @@ class IconTest < ActiveSupport::TestCase logo = create(:attachment) project = create(:project, logo_id: logo.id) - image = '\"img" - _(Icon.new(project).image).must_equal image + icon = Icon.new(project) + result = icon.image + + # Check that it's wrapped in a div with icon-container and has-logo classes + _(result).must_match(/