cart-contents-block/title-style-chunk', 'wc-blocks-mini-cart-contents-block/checkout-button-style-chunk', 'wc-blocks-mini-cart-contents-block/title-label-frontend-chunk', 'wc-blocks-mini-cart-contents-block/title-label-style-chunk', 'wc-blocks-mini-cart-contents-block/title-items-counter-frontend-chunk', 'wc-blocks-mini-cart-contents-block/items-frontend-chunk', 'wc-blocks-mini-cart-contents-block/items-style-chunk', 'wc-blocks-mini-cart-contents-block/cart-button-frontend-chunk', 'wc-blocks-mini-cart-contents-block/products-table-frontend-chunk', 'wc-blocks-mini-cart-contents-block/title-frontend-chunk', 'wc-blocks-mini-cart-contents-block/empty-cart-frontend-chunk', 'wc-blocks-mini-cart-contents-block/filled-cart-frontend-chunk', 'wc-blocks-mini-cart-contents-block/empty-cart-style-chunk', 'wc-blocks-mini-cart-contents-block/cart-button-style-chunk', 'wc-blocks-mini-cart-contents-block/footer-style-chunk', 'wc-blocks-mini-cart-contents-block/shopping-button-frontend-chunk', 'wc-blocks-mini-cart-contents-block/title-items-counter-style-chunk', 'wc-blocks-mini-cart-contents-block/filled-cart-style-chunk', 'wc-blocks-cart-blocks/cart-line-items--mini-cart-contents-block/products-table-frontend-chunk', ]; foreach ($handles as $handle) { wp_deregister_script($handle); } }, 100); // --------------------------------------------------------------------------- // Hook profiler — identify slow PHP execution on any page. // // Activate by appending ?pfm_profile=pfm2026 to any URL, e.g.: // https://www.particleformen.com/cart/?pfm_profile=pfm2026 // // Output: wp-content/pfm-hook-profiler.log // // Each run appends a block showing the top hooks by total ms consumed. // Time is attributed to the hook that was *running* when the next hook fired, // so a hook with high ms means its callbacks (across all plugins) are slow. // // Disable: change the secret below or remove this block. // --------------------------------------------------------------------------- (static function () { if (php_sapi_name() === 'cli') return; if (($_GET['pfm_profile'] ?? '') !== 'pfm2026') return; $log = []; $prev = ['hook' => 'BOOT', 'time' => microtime(true)]; add_action('all', static function ($tag) use (&$log, &$prev) { $now = microtime(true); $elapsed = ($now - $prev['time']) * 1000; $key = $prev['hook']; if (!isset($log[$key])) { $log[$key] = ['ms' => 0.0, 'calls' => 0]; } $log[$key]['ms'] += $elapsed; $log[$key]['calls'] += 1; $prev = ['hook' => $tag, 'time' => $now]; }, PHP_INT_MIN); add_action('shutdown', static function () use (&$log, &$prev) { $now = microtime(true); $elapsed = ($now - $prev['time']) * 1000; $key = $prev['hook']; if (!isset($log[$key])) { $log[$key] = ['ms' => 0.0, 'calls' => 0]; } $log[$key]['ms'] += $elapsed; $log[$key]['calls'] += 1; uasort($log, static fn($a, $b) => $b['ms'] <=> $a['ms']); $total_ms = array_sum(array_column($log, 'ms')); $lines = []; $lines[] = sprintf( '=== PFM Hook Profiler %s %s (total: %.0f ms) ===', date('Y-m-d H:i:s'), ($_SERVER['REQUEST_URI'] ?? '-'), $total_ms ); $lines[] = sprintf('%9s %6s %s', 'ms', 'calls', 'hook'); $lines[] = str_repeat('-', 80); $shown = 0; foreach ($log as $hook => $data) { if ($shown >= 60) break; if ($data['ms'] < 0.5 && $shown > 40) break; $lines[] = sprintf('%8.2f ms %5dx %s', $data['ms'], $data['calls'], $hook); $shown++; } $lines[] = ''; file_put_contents( WP_CONTENT_DIR . '/pfm-hook-profiler.log', implode("\n", $lines) . "\n", FILE_APPEND | LOCK_EX ); }, PHP_INT_MAX - 1); })(); // --------------------------------------------------------------------------- // Query logger — identify slow or redundant DB queries on any page. // // Activate by appending ?pfm_profile=pfm2026 to any URL, e.g.: // https://www.particleformen.com/cart/?pfm_profile=pfm2026 // // Output: wp-content/pfm-query-log.log // // Each run appends a block showing all queries sorted by time (slowest first), // with the SQL, duration, and a short backtrace to identify the caller. // Only queries taking >= 1 ms are shown to keep the log readable. // --------------------------------------------------------------------------- (static function () { if (php_sapi_name() === 'cli') return; if (($_GET['pfm_profile'] ?? '') !== 'pfm2026') return; // Must enable SAVEQUERIES before any queries run. if (!defined('SAVEQUERIES')) define('SAVEQUERIES', true); add_action('shutdown', static function () { global $wpdb; if (empty($wpdb->queries)) return; $queries = $wpdb->queries; // Sort slowest first. usort($queries, static fn($a, $b) => $b[1] <=> $a[1]); $total_ms = array_sum(array_column($queries, 1)) * 1000; $total_q = count($queries); $lines = []; $lines[] = sprintf( '=== PFM Query Log %s %s (%d queries, total: %.0f ms) ===', date('Y-m-d H:i:s'), $_SERVER['REQUEST_URI'] ?? '-', $total_q, $total_ms ); $lines[] = str_repeat('-', 80); $shown = 0; foreach ($queries as [$sql, $duration, $caller]) { $ms = $duration * 1000; if ($ms < 1.0 && $shown > 20) break; // skip sub-1ms after first 20 $sql_short = preg_replace('/\s+/', ' ', trim($sql)); if (strlen($sql_short) > 200) $sql_short = substr($sql_short, 0, 200) . '…'; $caller_short = implode(' → ', array_slice(array_filter(explode(',', $caller)), 0, 4)); $lines[] = sprintf('%7.2f ms %s', $ms, $sql_short); $lines[] = sprintf(' caller: %s', trim($caller_short)); $lines[] = ''; $shown++; if ($shown >= 60) break; } file_put_contents( WP_CONTENT_DIR . '/pfm-query-log.log', implode("\n", $lines) . "\n", FILE_APPEND | LOCK_EX ); }, PHP_INT_MAX - 2); })(); // --------------------------------------------------------------------------- // Discount Rules Pro — per-request cache for all three Base::__construct() // filters that drive repeated translation overhead. // // Root cause (confirmed by deep codebase analysis June 24 2026): // Base::__construct() is called ~17× per cart page (Settings, Messages, // WDRAjax, Statistics, 3× ManageDiscount→DiscountCalculator, ShortCode…). // Three instance-property arrays are rebuilt on every construction because // they use instance properties (not static), so each new instance starts // from []: // // • filtersTypes() → 'advanced_woo_discount_rules_filters' // Filters.php::addFilters() translates ~10 label strings per call. // // • getAvailableConditions() → 'advanced_woo_discount_rules_conditions' // Conditions::addConditional() scandir + new for ~32 Pro condition // classes; every constructor calls __() twice (label + group). // // • discountElements() → 'advanced_woo_discount_rules_adjustment_type' // 5 Pro callbacks (BuyXGetY, BuyXGetX, FreeShipping, Set, ProOptions) // each translate their discount type labels. // // Total: ~1,370 gettext calls ≈ 58ms per cart page, ~130 gettext calls per // ATC AJAX (same boot sequence, fewer hooks fired). // // Fix — same pattern for all three filters: // At PHP_INT_MAX (after every pro/core callback has already run and fully // built the array), call remove_all_filters() and register a single // zero-work cache-return closure. From call #2 onward in the same request, // only that closure executes: no condition constructors are instantiated, // no __() calls are made, no filesystem scans happen. // // Safety: // • Condition objects are already shared by design — all Rule instances // reference the same objects stored in ManageDiscount::$available_rules. // • None of the three callbacks register hooks; they only build arrays. // • Cache is per-request (PHP process), so admin/CLI/cron always start fresh. // • Admin AJAX and OnSaleShortCode also benefit — they hit the cache on // their 2nd+ Base construction within the same request. // --------------------------------------------------------------------------- add_filter('advanced_woo_discount_rules_filters', static function ($filter_types) { remove_all_filters('advanced_woo_discount_rules_filters'); add_filter('advanced_woo_discount_rules_filters', static function () use ($filter_types) { return $filter_types; }, 0); return $filter_types; }, PHP_INT_MAX); add_filter('advanced_woo_discount_rules_conditions', static function ($available_conditions) { remove_all_filters('advanced_woo_discount_rules_conditions'); add_filter('advanced_woo_discount_rules_conditions', static function () use ($available_conditions) { return $available_conditions; }, 0); return $available_conditions; }, PHP_INT_MAX); add_filter('advanced_woo_discount_rules_adjustment_type', static function ($discount_types) { remove_all_filters('advanced_woo_discount_rules_adjustment_type'); add_filter('advanced_woo_discount_rules_adjustment_type', static function () use ($discount_types) { return $discount_types; }, 0); return $discount_types; }, PHP_INT_MAX); // --------------------------------------------------------------------------- // Discount Rules Pro gettext tracer — find what drives 1,370 translation // calls per cart page load (gettext_woo-discount-rules-pro at ~58ms). // // Activate: ?pfm_profile=pfm2026 // Output: wp-content/pfm-dr-gettext.log // // Captures: total call count, unique strings being translated, and up to 10 // unique call stacks to identify which DR Pro function is the main caller. // --------------------------------------------------------------------------- (static function () { if (php_sapi_name() === 'cli') return; if (($_GET['pfm_profile'] ?? '') !== 'pfm2026') return; $strings = []; // [text => count] $stacks = []; // [fingerprint => trace string] add_filter('gettext_woo-discount-rules-pro', static function ($translation, $text) use (&$strings, &$stacks) { $strings[$text] = ($strings[$text] ?? 0) + 1; if (count($stacks) < 10) { $frames = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); // Skip WordPress/PHP internals — find first DR Pro frame $dr_frame = null; foreach ($frames as $f) { if (isset($f['file']) && strpos($f['file'], 'woo-discount-rules') !== false) { $dr_frame = basename($f['file']) . ':' . ($f['line'] ?? '?') . ' ' . ($f['function'] ?? ''); break; } } $key = $dr_frame ?? 'unknown'; if (!isset($stacks[$key])) { $stacks[$key] = $key; } } return $translation; }, PHP_INT_MAX, 2); add_action('shutdown', static function () use (&$strings, &$stacks) { if (empty($strings)) return; arsort($strings); $total = array_sum($strings); $lines = []; $lines[] = sprintf( '=== DR Pro gettext tracer %s %s (%d total calls, %d unique strings) ===', gmdate('Y-m-d H:i:s'), $_SERVER['REQUEST_URI'] ?? '-', $total, count($strings) ); $lines[] = str_repeat('-', 80); $lines[] = 'Top 20 most-translated strings:'; $i = 0; foreach ($strings as $text => $count) { $lines[] = sprintf('%5dx %s', $count, substr($text, 0, 80)); if (++$i >= 20) break; } $lines[] = ''; $lines[] = 'Top DR Pro call locations (up to 10):'; foreach ($stacks as $trace) { $lines[] = ' ' . $trace; } $lines[] = ''; file_put_contents( WP_CONTENT_DIR . '/pfm-dr-gettext.log', implode("\n", $lines) . "\n", FILE_APPEND | LOCK_EX ); }, PHP_INT_MAX - 3); })(); // WPML URL converter tracer — REMOVED after analysis (June 24, 2026). // Findings: fires ~233x per cart request but always returns the same URL // (https://www.particleformen.com). WPML already caches $this->absolute_home // internally, so the only cost is bare apply_filters() PHP overhead (~0.2ms // total in production). The 19ms measured in profiler was tracer's own // debug_backtrace() overhead, not a real bottleneck. No optimization needed. // --------------------------------------------------------------------------- // Script translation file tracer — list which scripts load .json translation // files on the cart page, and which don't find a file at all. // // Activate: ?pfm_profile=pfm2026 // Output: wp-content/pfm-scripts-trace.log // // The profiler shows load_script_translation_file fires ~132x per request // (~13ms). This log reveals which handles are responsible so we can // selectively dequeue translations for scripts that don't need them. // --------------------------------------------------------------------------- (static function () { if (php_sapi_name() === 'cli') return; if (($_GET['pfm_profile'] ?? '') !== 'pfm2026') return; $found = []; // handle => file path $missing = []; // handle => searched path add_filter('load_script_translation_file', static function ($file, $handle, $domain) use (&$found, &$missing) { if ($file) { $found[$handle] = $file; } else { $missing[$handle] = $domain; } return $file; }, PHP_INT_MAX, 3); add_action('shutdown', static function () use (&$found, &$missing) { if (empty($found) && empty($missing)) return; $lines = []; $lines[] = sprintf( '=== PFM Script Translation Trace %s %s (%d found, %d missing) ===', date('Y-m-d H:i:s'), $_SERVER['REQUEST_URI'] ?? '-', count($found), count($missing) ); $lines[] = str_repeat('-', 80); $lines[] = sprintf('FOUND (%d):', count($found)); foreach ($found as $handle => $path) { $short = str_replace(ABSPATH, '', $path); $lines[] = sprintf(' %-45s %s', $handle, $short); } $lines[] = ''; $lines[] = sprintf('NOT FOUND / no file (%d):', count($missing)); foreach ($missing as $handle => $domain) { $lines[] = sprintf(' %-45s domain: %s', $handle, $domain); } $lines[] = ''; file_put_contents( WP_CONTENT_DIR . '/pfm-scripts-trace.log', implode("\n", $lines) . "\n", FILE_APPEND | LOCK_EX ); }, PHP_INT_MAX - 4); })(); // --------------------------------------------------------------------------- // WooCommerce Subscriptions — suppress failed scheduled action admin notice. // // WC Subscriptions stores failed payment retry events in the option // `woocommerce_subscriptions_failed_scheduled_actions` and renders an error // notice on EVERY admin page load. Rendering that notice queries yom_usermeta // and yom_wc_orders to resolve subscription IDs — each query takes 1.4–4s, // and it runs for every admin user on every admin page. We monitor subscription // failures via other means so this notice has no operational value. // // pre_option_* short-circuits get_option() before the DB is hit — the notice // sees an empty array and returns immediately without any DB queries. // pre_update_option_* discards new writes so the option never grows back. // --------------------------------------------------------------------------- add_filter('pre_option_woocommerce_subscriptions_failed_scheduled_actions', '__return_empty_array'); add_filter('pre_update_option_woocommerce_subscriptions_failed_scheduled_actions', '__return_empty_array'); // --------------------------------------------------------------------------- // Action Scheduler — prevent inline execution on web requests. // // By default AS hooks its queue runner into every WordPress request (init hook) // and runs a SELECT on actionscheduler_actions to check for due actions. This // makes it the #2 most expensive DB operation site-wide (11.87%), hitting // cart, thank-you page, and AJAX transactions on every request. // // WP Engine runs real system cron every minute, so AS jobs are processed // reliably without needing to piggyback on web requests. Returning 0 batches // for non-cron/non-CLI requests prevents the runner from firing inline — no // SELECT, no claim UPDATE, no per-request overhead. // --------------------------------------------------------------------------- // Safety net: prevent AS inline execution on web requests in case WP Engine's // DISABLE_WP_CRON setting ever changes. Confirmed via diagnostic logging that // AS already only fires on cli/cron on WP Engine — this filter is redundant // but harmless. The real saving is the 2-day retention filter below. add_filter('action_scheduler_queue_runner_concurrent_batches', static function ($batches) { if (defined('DOING_CRON') && DOING_CRON) return $batches; if (defined('WP_CLI') && WP_CLI) return $batches; if (defined('DOING_AJAX') && DOING_AJAX && ($_REQUEST['action'] ?? '') === 'action_scheduler_run') return $batches; return 0; }); // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // wp_load_alloptions() PHP-memory cache — restore WP 6.6 regression. // // Root cause (confirmed June 24 2026, WordPress 6.6.1): // WordPress 6.6 removed the $wp_options global that previously cached // alloptions in PHP memory. The new implementation always calls // wp_cache_get('alloptions', 'options') — meaning every get_option() call // that isn't short-circuited by pre_option makes a full Redis round-trip. // On a cart page this amounts to ~2,418 Redis calls per request (~50ms). // // Fix: after the first real load, return the result from PHP memory for all // subsequent calls in the same request — exactly what the old $wp_options // global used to do. // // Invalidation: hook updated_option / added_option / deleted_option (which // fire AFTER wp_cache_delete('alloptions') in update_option()) to clear the // PHP cache, ensuring any in-request option mutation is immediately visible. // --------------------------------------------------------------------------- (static function () { $cache = null; $ready = false; // After the first real load, capture the complete alloptions array. add_filter('alloptions', static function ($options) use (&$cache, &$ready) { if (!$ready) { $ready = true; $cache = $options; } return $options; }, PHP_INT_MAX); // Return PHP-memory cache for all subsequent calls (before Redis is hit). // $force_cache = true means the caller explicitly wants a fresh Redis fetch // (e.g. after a cross-process option update) — respect that and pass through. add_filter('pre_wp_load_alloptions', static function ($pre, $force_cache) use (&$cache, &$ready) { if ($ready && !$force_cache) { return $cache; // non-null → WordPress short-circuits immediately } if ($force_cache) { // Forced refresh — invalidate so next non-forced call re-captures $ready = false; $cache = null; } return $pre; // null → proceed to Redis }, 1, 2); // Invalidate whenever any option is mutated within the same request. // By the time these actions fire, wp_cache_delete('alloptions') has // already run, so the next load will get fresh data from Redis/DB. $invalidate = static function () use (&$cache, &$ready) { $ready = false; $cache = null; }; add_action('updated_option', $invalidate, 1); add_action('added_option', $invalidate, 1); add_action('deleted_option', $invalidate, 1); })(); // --------------------------------------------------------------------------- // alloptions call tracer — verify the fix above (gated behind profiler flag). // // Activate: ?pfm_profile=pfm2026 // Output: wp-content/pfm-alloptions.log // // Before fix: both counters show ~2,418 (every call hit Redis). // After fix: pre_count ~2,418, load_count ~1 (only first call loads; the // rest are served from PHP memory by the cache above). // --------------------------------------------------------------------------- (static function () { if (php_sapi_name() === 'cli') return; if (($_GET['pfm_profile'] ?? '') !== 'pfm2026') return; $pre_count = 0; $load_count = 0; add_filter('pre_wp_load_alloptions', static function ($pre) use (&$pre_count) { $pre_count++; return $pre; }, 0); // priority 0 — before our cache at priority 1 add_filter('alloptions', static function ($options) use (&$load_count) { $load_count++; return $options; }, 0); // priority 0 — before our cache capture at PHP_INT_MAX add_action('shutdown', static function () use (&$pre_count, &$load_count) { $lines = []; $lines[] = sprintf( '=== alloptions tracer %s %s ===', gmdate('Y-m-d H:i:s'), $_SERVER['REQUEST_URI'] ?? '-' ); $lines[] = sprintf( 'wp_load_alloptions() called: %d× | options actually loaded (Redis/DB): %d×', $pre_count, $load_count ); $lines[] = sprintf( 'PHP-memory cache hits: %d× (expected ~%d)', max(0, $pre_count - $load_count), max(0, $pre_count - 1) ); $lines[] = ''; file_put_contents( WP_CONTENT_DIR . '/pfm-alloptions.log', implode("\n", $lines) . "\n", FILE_APPEND | LOCK_EX ); }, PHP_INT_MAX - 3); })(); // --------------------------------------------------------------------------- // Action Scheduler — reduce completed-action retention from 31 days to 2 days. // // The actionscheduler_actions table accumulates ~46K completed rows/day from // webhook deliveries, order API logs, and WC admin imports. At the default // 31-day retention this grows to 156K+ rows, making every AS SELECT slow. // 2-day retention keeps the table at ~92K completed rows — enough for any // debugging window, far less database overhead. // --------------------------------------------------------------------------- add_filter('action_scheduler_retention_period', static fn() => 2 * DAY_IN_SECONDS); Winter Skincare for Men: What You Need to Know - Particle
Free Shipping + 30 Day Money Back Guarantee

Magazine

Winter Skincare for Men: What You Need to Know

Brian Melville, author
Posted by Brian Melville
Winter Skincare for Men: What You Need to Know

There are no two ways about it, winter is a hard season. It’s hard on your mental state, your energy bills, and even your skin.

Cold winter air can be incredibly irritating and harsh, leaving you looking sallow, worn out, and run down.

Fortunately, you’re not doomed to peeling, unhappy skin all winter long. With the right skincare routine, men’s facial products, and tips, you can keep your skin glowing and happy right through the cold months.

Keep reading for our top wintertime skincare tips.

Why a Winter Skincare Routine Is Important

A solid skincare routine is key all year round, but it’s especially critical when the temperatures drop. During winter, humidity levels plummet.

Dry air sucks the moisture out of the top layer of your skin. This can rob your skin of its plumpness and negatively impact your moisture barrier.

If your moisture barrier is depleted, your skin can lose some of its elasticity and become rough, irritated, dry, and flaky.

Dry skin can also cause flare-ups in skin conditions like eczema, rosacea, and acne.

If you’re past the flush of youth and are dealing with fine lines, wrinkles, and slight sagging, the drying effects of winter can make these issues even worse. With less moisture to fill out your skin, lines and wrinkles can look more apparent, and sagging skin can become more pronounced.

Winter can also affect the color of your skin. Irritation can trigger redness, which is never a suave look, and lower vitamin D levels can leave your skin looking sallow.

Hydration Is the Name of the Game

Because winter is so drying, your first line of defense is moisturizing. But to really get results, you need a good moisturizer.

The best moisturizer products not only add hydration back into the skin, but also lock it in. To make sure your moisturizer delivers on all fronts, look for a mixture of hydrating ingredients combined with a couple of ultra-rich occlusives like jojoba oil and shea butter.

Moisturizers rehydrate skin through their water content and through humectant ingredients. Humectants attract water and help draw moisture into your skin. Hyaluronic acid and glycerin are two highly effective humectants to vet for in a moisturizer.

Glycerin is a powerful humectant that can make the skin smoother. Hyaluronic acid doesn’t just provide moisture to the skin; it can also help wounds heal faster, make scars fade quicker, and render the skin more elastic.

If your current moisturizer doesn’t quite cut it, and you’re trying to find a new one, take a look at the Particle Face Cream for men.

It’s hydrating and rich enough for winter, without being oily and clogging the skin.

Seek Out Soothing Ingredients

Along with hydration, you should also leverage soothing ingredients that can help counteract irritation triggered by dry, cold air.

A few to focus on include:

  • Centella
  • Jojoba oil
  • Allantoin
  • Squalene oil
  • Pentavitin

These ingredients can help calm skin redness, reduce stinging sensations, and soothe your barrier. If you suffer from conditions like dermatitis, eczema, or rosacea, calming ingredients are extra important.

If you shave, soothing substances applied afterward can help you ward off the dreaded razor rash.

Here at Particle, we know you don’t have time to apply a million serums and skincare products, which is why the Particle Face Cream contains a set of soothing ingredients, including squalene oil and allantoin.

Your Facial Cleanser Could Be Exacerbating Dryness

Dry air isn’t the only thing that can strip your skin of moisture. Your cleanser could also be to blame. Harsh cleansers suck the moisture out of one’s skin, leaving it dry and tight.

If you feel like your skin is pulling on your face after you wash, your cleanser is probably too harsh. Body washes and bar soaps are even worse, and should never come near your face.

Look for a hydrating facial cleanser that will replenish moisture and feed your skin while stripping away dirt and excess oil.

Excessively Hot Showers Are Another Drying Factor

Super hot showers are another no-no if you’re suffering from dry skin in winter, as hot water can dehydrate the skin barrier.

Of course, there’s nothing like a piping hot shower when you come in from the cold, but if you have to turn the hot water tap up, don’t do it on your face. Instead, lower the water temperature for your face and head.

Your Lips Need Extra Love

Did you know that the skin on your lips doesn’t have pores? Without pores, it can’t sweat or secrete oil to protect the moisture barrier. This is why lips are so prone to dryness, cracking, and chapping.

The dryer your lips become, the more likely you are to lick them, which can exacerbate the situation and lead to what’s known as ‘lick eczema.’ To avoid this, make sure you carry chapstick and keep your lips protected.

Your Neck and Body Also Deserve Hydration

Your lips and face aren’t the only areas that can get dry in winter. The skin on your body might also become rough and flaky.

Fortunately, a good body cream is all you need to stop the shed. You can use this on your neck, or you can try a targeted product designed to address issues like sagging skin and wrinkles, such as the Particle Neck Cream.

Healthy Skin Is an Inside Job

Great skincare products and a good skincare routine are key, but healthy skin also starts on the side. If you feel like your skin is down in condition, you might be low in a few key nutrients, such as vitamin C or zinc.

To combat this, you take a multivitamin or a vitamin gummy complex specifically designed for skin health.

We Specialize in Science-Backed Men’s Facial Products

When temperatures and humidity levels drop, it’s time to up your skincare game. Focus on the best moisturizer ingredients that will deliver and lock in hydration. Evaluate your cleanser and your nutrient levels, and carry chapstick.

Here at Particle, we believe men’s skincare doesn’t have to be complicated. That’s why we’ve developed a core range of men’s facial products that deliver hard. Backed by years of dermatological testing, our skincare line is science-driven.

Browse our line here.