twofoldtech-dakota

frontend-classic

Apply when working with classic frontend technologies including CSS, SASS, JavaScript, and jQuery

twofoldtech-dakota 2 1 Updated 7mo ago
GitHub

Install

npx skillscat add twofoldtech-dakota/claude-marketplace/frontend-classic

Install via the SkillsCat registry.

About this skill

Provides a structured approach to organizing CSS and SASS files, JavaScript, and jQuery code using a 7‑1 pattern and defined import order. It solves maintainability issues in classic frontend projects by enforcing consistent file layout and dependency management. Use it when developing or maintaining applications that rely on traditional CSS, SASS, JavaScript, and jQuery.

SKILL.md

Classic Frontend Development Patterns

CSS/SASS Organization

File Structure (7-1 Pattern)

styles/
├── abstracts/
│   ├── _variables.scss      # Colors, fonts, breakpoints
│   ├── _mixins.scss         # Reusable mixins
│   └── _functions.scss      # SASS functions
├── base/
│   ├── _reset.scss          # CSS reset/normalize
│   ├── _typography.scss     # Base typography
│   └── _utilities.scss      # Utility classes
├── components/
│   ├── _buttons.scss        # Button styles
│   ├── _forms.scss          # Form elements
│   └── _cards.scss          # Card components
├── layout/
│   ├── _header.scss         # Header layout
│   ├── _footer.scss         # Footer layout
│   ├── _grid.scss           # Grid system
│   └── _navigation.scss     # Navigation
├── pages/
│   ├── _home.scss           # Home page specific
│   └── _contact.scss        # Contact page specific
├── themes/
│   └── _default.scss        # Default theme
├── vendors/
│   └── _bootstrap.scss      # Third-party overrides
└── main.scss                # Main import file

Main SCSS Import Order

// main.scss
@import 'abstracts/variables';
@import 'abstracts/mixins';
@import 'abstracts/functions';

@import 'vendors/bootstrap';

@import 'base/reset';
@import 'base/typography';

@import 'layout/grid';
@import 'layout/header';
@import 'layout/footer';
@import 'layout/navigation';

@import 'components/buttons';
@import 'components/forms';
@import 'components/cards';

@import 'pages/home';
@import 'pages/contact';

@import 'themes/default';

@import 'base/utilities'; // Last for override capability

SASS Variables and Mixins

Color System

// _variables.scss
$color-primary: #0066cc;
$color-primary-dark: darken($color-primary, 10%);
$color-primary-light: lighten($color-primary, 10%);

$color-secondary: #6c757d;
$color-success: #28a745;
$color-warning: #ffc107;
$color-danger: #dc3545;

$color-text: #333333;
$color-text-muted: #6c757d;
$color-background: #ffffff;
$color-border: #dee2e6;

// Semantic aliases
$color-link: $color-primary;
$color-link-hover: $color-primary-dark;

Typography Variables

// _variables.scss
$font-family-base: 'Open Sans', -apple-system, BlinkMacSystemFont, sans-serif;
$font-family-heading: 'Montserrat', $font-family-base;

$font-size-base: 16px;
$font-size-sm: 14px;
$font-size-lg: 18px;

$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-bold: 700;

$line-height-base: 1.5;
$line-height-heading: 1.2;

Responsive Breakpoints

// _variables.scss
$breakpoint-xs: 0;
$breakpoint-sm: 576px;
$breakpoint-md: 768px;
$breakpoint-lg: 992px;
$breakpoint-xl: 1200px;
$breakpoint-xxl: 1400px;

$breakpoints: (
  'xs': $breakpoint-xs,
  'sm': $breakpoint-sm,
  'md': $breakpoint-md,
  'lg': $breakpoint-lg,
  'xl': $breakpoint-xl,
  'xxl': $breakpoint-xxl
);

Essential Mixins

// _mixins.scss

// Responsive breakpoint mixin
@mixin respond-to($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    @media (min-width: map-get($breakpoints, $breakpoint)) {
      @content;
    }
  } @else {
    @warn "Unknown breakpoint: #{$breakpoint}";
  }
}

// Flexbox center
@mixin flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

// Clearfix
@mixin clearfix {
  &::after {
    content: '';
    display: table;
    clear: both;
  }
}

// Truncate text with ellipsis
@mixin text-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

// Visually hidden (accessible)
@mixin visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

// Button reset
@mixin button-reset {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font: inherit;
  color: inherit;
}

Using Mixins

// Example component
.hero {
  padding: 2rem;
  
  @include respond-to('md') {
    padding: 4rem;
  }
  
  @include respond-to('lg') {
    padding: 6rem;
  }
  
  &__content {
    @include flex-center;
    flex-direction: column;
  }
  
  &__title {
    @include text-truncate;
    max-width: 100%;
  }
}

BEM Naming Convention

Block, Element, Modifier

// Block
.card { }

// Element (double underscore)
.card__header { }
.card__body { }
.card__footer { }
.card__title { }
.card__image { }

// Modifier (double hyphen)
.card--featured { }
.card--compact { }
.card__title--large { }

BEM Example

.navigation {
  display: flex;
  background: $color-background;
  
  &__list {
    display: flex;
    list-style: none;
    margin: 0;
    padding: 0;
  }
  
  &__item {
    margin: 0 1rem;
  }
  
  &__link {
    color: $color-text;
    text-decoration: none;
    
    &:hover {
      color: $color-primary;
    }
    
    &--active {
      color: $color-primary;
      font-weight: $font-weight-bold;
    }
  }
  
  // Modifier for dark theme
  &--dark {
    background: #333;
    
    .navigation__link {
      color: #fff;
    }
  }
}

JavaScript Patterns

Namespace Pattern

// Avoid global pollution
var MYAPP = MYAPP || {};

MYAPP.navigation = {
    init: function() {
        this.bindEvents();
    },
    
    bindEvents: function() {
        $('.navigation__toggle').on('click', this.toggle.bind(this));
    },
    
    toggle: function(e) {
        e.preventDefault();
        $('.navigation__menu').toggleClass('is-open');
    }
};

// Initialize
$(document).ready(function() {
    MYAPP.navigation.init();
});

Module Pattern

var MYAPP = MYAPP || {};

MYAPP.modal = (function($) {
    // Private variables
    var $modal = null;
    var isOpen = false;
    
    // Private functions
    function bindEvents() {
        $(document).on('click', '[data-modal-open]', open);
        $(document).on('click', '[data-modal-close]', close);
        $(document).on('click', '.modal__overlay', close);
        $(document).on('keydown', handleKeydown);
    }
    
    function handleKeydown(e) {
        if (e.key === 'Escape' && isOpen) {
            close();
        }
    }
    
    // Public functions
    function init() {
        bindEvents();
    }
    
    function open(e) {
        e.preventDefault();
        var target = $(this).data('modal-open');
        $modal = $('#' + target);
        $modal.addClass('is-visible');
        isOpen = true;
        $('body').addClass('modal-open');
    }
    
    function close() {
        if ($modal) {
            $modal.removeClass('is-visible');
            isOpen = false;
            $('body').removeClass('modal-open');
        }
    }
    
    // Expose public API
    return {
        init: init,
        open: open,
        close: close
    };
})(jQuery);

Document Ready Pattern

// Short syntax
$(function() {
    // DOM ready
    MYAPP.init();
});

// Full syntax (preferred for clarity)
$(document).ready(function() {
    MYAPP.init();
});

// When also waiting for images
$(window).on('load', function() {
    // All assets loaded
    MYAPP.initAfterLoad();
});

jQuery Best Practices

Cache Selectors

// BAD - queries DOM multiple times
$('.header').addClass('sticky');
$('.header').find('.nav').show();
$('.header').attr('data-visible', 'true');

// GOOD - cache the selector
var $header = $('.header');
$header.addClass('sticky');
$header.find('.nav').show();
$header.attr('data-visible', 'true');

// BETTER - chain methods
$('.header')
    .addClass('sticky')
    .find('.nav').show()
    .end()
    .attr('data-visible', 'true');

Event Delegation

// BAD - binds to each element (memory heavy)
$('.item').on('click', function() {
    $(this).toggleClass('active');
});

// GOOD - delegate from parent (handles dynamic elements)
$('.item-list').on('click', '.item', function() {
    $(this).toggleClass('active');
});

// For document-level delegation
$(document).on('click', '[data-action="delete"]', function(e) {
    e.preventDefault();
    var id = $(this).data('id');
    deleteItem(id);
});

AJAX Patterns

// Basic GET request
$.get('/api/items', function(data) {
    renderItems(data);
});

// POST with data
$.post('/api/items', { name: 'New Item' }, function(response) {
    showSuccess('Item created');
});

// Full AJAX with error handling
$.ajax({
    url: '/api/items',
    type: 'POST',
    contentType: 'application/json',
    data: JSON.stringify({ name: 'New Item' }),
    beforeSend: function() {
        showLoader();
    },
    success: function(response) {
        showSuccess('Item created');
        refreshList();
    },
    error: function(xhr, status, error) {
        showError('Failed to create item: ' + error);
    },
    complete: function() {
        hideLoader();
    }
});

Form Handling

// Form submission with AJAX
$('#contact-form').on('submit', function(e) {
    e.preventDefault();
    
    var $form = $(this);
    var $submit = $form.find('[type="submit"]');
    var formData = $form.serialize();
    
    // Disable button during submission
    $submit.prop('disabled', true).text('Sending...');
    
    $.ajax({
        url: $form.attr('action'),
        type: $form.attr('method') || 'POST',
        data: formData,
        success: function(response) {
            $form[0].reset();
            showSuccess('Message sent successfully');
        },
        error: function(xhr) {
            var errors = xhr.responseJSON;
            showErrors(errors);
        },
        complete: function() {
            $submit.prop('disabled', false).text('Send');
        }
    });
});

// Client-side validation
function validateForm($form) {
    var isValid = true;
    
    $form.find('[required]').each(function() {
        var $field = $(this);
        var value = $field.val().trim();
        
        if (!value) {
            $field.addClass('is-invalid');
            isValid = false;
        } else {
            $field.removeClass('is-invalid');
        }
    });
    
    return isValid;
}

Animation and Transitions

// Fade animations
$('.notification').fadeIn(300);
$('.notification').fadeOut(300, function() {
    $(this).remove();
});

// Slide animations
$('.accordion__content').slideToggle(200);

// Custom animation
$('.element').animate({
    opacity: 0.5,
    marginLeft: '20px'
}, 300);

// Using CSS classes (preferred for performance)
$('.element').addClass('is-visible');
// Combined with CSS transition
// .element { transition: opacity 0.3s ease; }
// .element.is-visible { opacity: 1; }

Responsive Patterns

Mobile-First CSS

// Start with mobile styles
.container {
  padding: 1rem;
  
  // Tablet and up
  @include respond-to('md') {
    padding: 2rem;
  }
  
  // Desktop and up
  @include respond-to('lg') {
    padding: 3rem;
    max-width: 1200px;
    margin: 0 auto;
  }
}

Responsive Images

.responsive-image {
  max-width: 100%;
  height: auto;
}

// Background image responsive
.hero {
  background-image: url('/images/hero-mobile.jpg');
  background-size: cover;
  background-position: center;
  
  @include respond-to('md') {
    background-image: url('/images/hero-tablet.jpg');
  }
  
  @include respond-to('lg') {
    background-image: url('/images/hero-desktop.jpg');
  }
}

Print Styles

@media print {
  // Hide non-essential elements
  .navigation,
  .footer,
  .sidebar,
  .btn {
    display: none !important;
  }
  
  // Ensure content is readable
  body {
    font-size: 12pt;
    line-height: 1.4;
    color: #000;
    background: #fff;
  }
  
  // Show URLs for links
  a[href]::after {
    content: ' (' attr(href) ')';
  }
  
  // Avoid page breaks inside elements
  h1, h2, h3, h4 {
    page-break-after: avoid;
  }
  
  img, table, figure {
    page-break-inside: avoid;
  }
}

Asset Management

Script Loading Order

<!-- In head: Critical CSS only -->
<link rel="stylesheet" href="/css/critical.css">

<!-- Defer non-critical CSS -->
<link rel="preload" href="/css/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">

<!-- Before closing body: Scripts -->
<script src="/js/vendor/jquery.min.js"></script>
<script src="/js/vendor/plugins.js"></script>
<script src="/js/main.js"></script>

Cache Busting

<!-- Version query string -->
<link rel="stylesheet" href="/css/main.css?v=1.2.3">
<script src="/js/main.js?v=1.2.3"></script>

<!-- Or hash-based (build tool generated) -->
<link rel="stylesheet" href="/css/main.a1b2c3d4.css">
<script src="/js/main.e5f6g7h8.js"></script>