')
- .append(i.clone())
- .remove()
- .html()
- .replace(/type="password"/i, 'type="text"')
- .replace(/type=password/i, 'type=text')
- );
-
- if (i.attr('id') != '')
- x.attr('id', i.attr('id') + '-polyfill-field');
-
- if (i.attr('name') != '')
- x.attr('name', i.attr('name') + '-polyfill-field');
-
- x.addClass('polyfill-placeholder')
- .val(x.attr('placeholder')).insertAfter(i);
-
- if (i.val() == '')
- i.hide();
- else
- x.hide();
-
- i
- .on('blur', function(event) {
-
- event.preventDefault();
-
- var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
-
- if (i.val() == '') {
-
- i.hide();
- x.show();
-
- }
-
- });
-
- x
- .on('focus', function(event) {
-
- event.preventDefault();
-
- var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']');
-
- x.hide();
-
- i
- .show()
- .focus();
-
- })
- .on('keypress', function(event) {
-
- event.preventDefault();
- x.val('');
-
- });
-
- });
-
- // Events.
- $this
- .on('submit', function() {
-
- $this.find('input[type=text],input[type=password],textarea')
- .each(function(event) {
-
- var i = $(this);
-
- if (i.attr('name').match(/-polyfill-field$/))
- i.attr('name', '');
-
- if (i.val() == i.attr('placeholder')) {
-
- i.removeClass('polyfill-placeholder');
- i.val('');
-
- }
-
- });
-
- })
- .on('reset', function(event) {
-
- event.preventDefault();
-
- $this.find('select')
- .val($('option:first').val());
-
- $this.find('input,textarea')
- .each(function() {
-
- var i = $(this),
- x;
-
- i.removeClass('polyfill-placeholder');
-
- switch (this.type) {
-
- case 'submit':
- case 'reset':
- break;
-
- case 'password':
- i.val(i.attr('defaultValue'));
-
- x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
-
- if (i.val() == '') {
- i.hide();
- x.show();
- }
- else {
- i.show();
- x.hide();
- }
-
- break;
-
- case 'checkbox':
- case 'radio':
- i.attr('checked', i.attr('defaultValue'));
- break;
-
- case 'text':
- case 'textarea':
- i.val(i.attr('defaultValue'));
-
- if (i.val() == '') {
- i.addClass('polyfill-placeholder');
- i.val(i.attr('placeholder'));
- }
-
- break;
-
- default:
- i.val(i.attr('defaultValue'));
- break;
-
- }
- });
-
- });
-
- return $this;
-
- };
-
- /**
- * Moves elements to/from the first positions of their respective parents.
- * @param {jQuery} $elements Elements (or selector) to move.
- * @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations.
- */
- $.prioritize = function($elements, condition) {
-
- var key = '__prioritize';
-
- // Expand $elements if it's not already a jQuery object.
- if (typeof $elements != 'jQuery')
- $elements = $($elements);
-
- // Step through elements.
- $elements.each(function() {
-
- var $e = $(this), $p,
- $parent = $e.parent();
-
- // No parent? Bail.
- if ($parent.length == 0)
- return;
-
- // Not moved? Move it.
- if (!$e.data(key)) {
-
- // Condition is false? Bail.
- if (!condition)
- return;
-
- // Get placeholder (which will serve as our point of reference for when this element needs to move back).
- $p = $e.prev();
-
- // Couldn't find anything? Means this element's already at the top, so bail.
- if ($p.length == 0)
- return;
-
- // Move element to top of parent.
- $e.prependTo($parent);
-
- // Mark element as moved.
- $e.data(key, $p);
-
- }
-
- // Moved already?
- else {
-
- // Condition is true? Bail.
- if (condition)
- return;
-
- $p = $e.data(key);
-
- // Move element back to its original location (using our placeholder).
- $e.insertAfter($p);
-
- // Unmark element as moved.
- $e.removeData(key);
-
- }
-
- });
-
- };
-
-})(jQuery);
\ No newline at end of file
diff --git a/assets/sass/ie8.scss b/assets/sass/ie8.scss
deleted file mode 100755
index 5a3822c49..000000000
--- a/assets/sass/ie8.scss
+++ /dev/null
@@ -1,87 +0,0 @@
-@import 'libs/vars';
-@import 'libs/functions';
-@import 'libs/mixins';
-@import 'libs/skel';
-
-/*
- Strata by HTML5 UP
- html5up.net | @ajlkn
- Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-*/
-
-/* Button */
-
- input[type="submit"],
- input[type="reset"],
- input[type="button"],
- .button {
- position: relative;
- -ms-behavior: url('assets/js/ie/PIE.htc');
- }
-
-/* Form */
-
- input[type="text"],
- input[type="password"],
- input[type="email"],
- select,
- textarea {
- position: relative;
- -ms-behavior: url('assets/js/ie/PIE.htc');
- }
-
- input[type="text"],
- input[type="password"],
- input[type="email"],
- select {
- height: _size(element-height);
- line-height: _size(element-height);
- }
-
- input[type="checkbox"],
- input[type="radio"] {
- & + label {
- &:before {
- display: none;
- }
- }
- }
-
-/* Image */
-
- .image {
- position: relative;
- -ms-behavior: url('assets/js/ie/PIE.htc');
-
- &:before, &:after {
- display: none !important;
- }
-
- img {
- position: relative;
- -ms-behavior: url('assets/js/ie/PIE.htc');
- }
- }
-
-/* Header */
-
- #header {
- background-image: url('../../images/bg.jpg');
- background-repeat: no-repeat;
- background-size: cover;
- -ms-behavior: url('assets/js/ie/backgroundsize.min.htc');
-
- h1 {
- color: _palette(accent2, fg-bold);
- }
- }
-
-/* Footer */
-
- #footer {
- .icons {
- a {
- color: _palette(accent2, fg-bold);
- }
- }
- }
\ No newline at end of file
diff --git a/assets/sass/libs/_functions.scss b/assets/sass/libs/_functions.scss
deleted file mode 100755
index 0e08c1a66..000000000
--- a/assets/sass/libs/_functions.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-/// Gets a duration value.
-/// @param {string} $keys Key(s).
-/// @return {string} Value.
-@function _duration($keys...) {
- @return val($duration, $keys...);
-}
-
-/// Gets a font value.
-/// @param {string} $keys Key(s).
-/// @return {string} Value.
-@function _font($keys...) {
- @return val($font, $keys...);
-}
-
-/// Gets a misc value.
-/// @param {string} $keys Key(s).
-/// @return {string} Value.
-@function _misc($keys...) {
- @return val($misc, $keys...);
-}
-
-/// Gets a palette value.
-/// @param {string} $keys Key(s).
-/// @return {string} Value.
-@function _palette($keys...) {
- @return val($palette, $keys...);
-}
-
-/// Gets a size value.
-/// @param {string} $keys Key(s).
-/// @return {string} Value.
-@function _size($keys...) {
- @return val($size, $keys...);
-}
\ No newline at end of file
diff --git a/assets/sass/libs/_mixins.scss b/assets/sass/libs/_mixins.scss
deleted file mode 100755
index 3325df0b0..000000000
--- a/assets/sass/libs/_mixins.scss
+++ /dev/null
@@ -1,56 +0,0 @@
-/// Makes an element's :before pseudoelement a FontAwesome icon.
-/// @param {string} $content Optional content value to use.
-/// @param {string} $where Optional pseudoelement to target (before or after).
-@mixin icon($content: false, $where: before) {
-
- text-decoration: none;
-
- &:#{$where} {
-
- @if $content {
- content: $content;
- }
-
- -moz-osx-font-smoothing: grayscale;
- -webkit-font-smoothing: antialiased;
- font-family: FontAwesome;
- font-style: normal;
- font-weight: normal;
- text-transform: none !important;
-
- }
-
-}
-
-/// Applies padding to an element, taking the current element-margin value into account.
-/// @param {mixed} $tb Top/bottom padding.
-/// @param {mixed} $lr Left/right padding.
-/// @param {list} $pad Optional extra padding (in the following order top, right, bottom, left)
-/// @param {bool} $important If true, adds !important.
-@mixin padding($tb, $lr, $pad: (0,0,0,0), $important: null) {
-
- @if $important {
- $important: '!important';
- }
-
- padding: ($tb + nth($pad,1)) ($lr + nth($pad,2)) max(0.1em, $tb - _size(element-margin) + nth($pad,3)) ($lr + nth($pad,4)) #{$important};
-
-}
-
-/// Encodes a SVG data URL so IE doesn't choke (via codepen.io/jakob-e/pen/YXXBrp).
-/// @param {string} $svg SVG data URL.
-/// @return {string} Encoded SVG data URL.
-@function svg-url($svg) {
-
- $svg: str-replace($svg, '"', '\'');
- $svg: str-replace($svg, '<', '%3C');
- $svg: str-replace($svg, '>', '%3E');
- $svg: str-replace($svg, '&', '%26');
- $svg: str-replace($svg, '#', '%23');
- $svg: str-replace($svg, '{', '%7B');
- $svg: str-replace($svg, '}', '%7D');
- $svg: str-replace($svg, ';', '%3B');
-
- @return url("data:image/svg+xml;charset=utf8,#{$svg}");
-
-}
\ No newline at end of file
diff --git a/assets/sass/libs/_skel.scss b/assets/sass/libs/_skel.scss
deleted file mode 100755
index 438b14797..000000000
--- a/assets/sass/libs/_skel.scss
+++ /dev/null
@@ -1,585 +0,0 @@
-// skel.scss v3.0.1 | (c) skel.io | MIT licensed */
-
-// Vars.
-
- /// Breakpoints.
- /// @var {list}
- $breakpoints: () !global;
-
- /// Vendor prefixes.
- /// @var {list}
- $vendor-prefixes: (
- '-moz-',
- '-webkit-',
- '-ms-',
- ''
- );
-
- /// Properties that should be vendorized.
- /// @var {list}
- $vendor-properties: (
- 'align-content',
- 'align-items',
- 'align-self',
- 'animation',
- 'animation-delay',
- 'animation-direction',
- 'animation-duration',
- 'animation-fill-mode',
- 'animation-iteration-count',
- 'animation-name',
- 'animation-play-state',
- 'animation-timing-function',
- 'appearance',
- 'backface-visibility',
- 'box-sizing',
- 'filter',
- 'flex',
- 'flex-basis',
- 'flex-direction',
- 'flex-flow',
- 'flex-grow',
- 'flex-shrink',
- 'flex-wrap',
- 'justify-content',
- 'order',
- 'perspective',
- 'pointer-events',
- 'transform',
- 'transform-origin',
- 'transform-style',
- 'transition',
- 'transition-delay',
- 'transition-duration',
- 'transition-property',
- 'transition-timing-function',
- 'user-select'
- );
-
- /// Values that should be vendorized.
- /// @var {list}
- $vendor-values: (
- 'filter',
- 'flex',
- 'linear-gradient',
- 'radial-gradient',
- 'transform'
- );
-
-// Functions.
-
- /// Removes a specific item from a list.
- /// @author Hugo Giraudel
- /// @param {list} $list List.
- /// @param {integer} $index Index.
- /// @return {list} Updated list.
- @function remove-nth($list, $index) {
-
- $result: null;
-
- @if type-of($index) != number {
- @warn "$index: #{quote($index)} is not a number for `remove-nth`.";
- }
- @else if $index == 0 {
- @warn "List index 0 must be a non-zero integer for `remove-nth`.";
- }
- @else if abs($index) > length($list) {
- @warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
- }
- @else {
-
- $result: ();
- $index: if($index < 0, length($list) + $index + 1, $index);
-
- @for $i from 1 through length($list) {
-
- @if $i != $index {
- $result: append($result, nth($list, $i));
- }
-
- }
-
- }
-
- @return $result;
-
- }
-
- /// Replaces a substring within another string.
- /// @author Hugo Giraudel
- /// @param {string} $string String.
- /// @param {string} $search Substring.
- /// @param {string} $replace Replacement.
- /// @return {string} Updated string.
- @function str-replace($string, $search, $replace: '') {
-
- $index: str-index($string, $search);
-
- @if $index {
- @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
- }
-
- @return $string;
-
- }
-
- /// Replaces a substring within each string in a list.
- /// @param {list} $strings List of strings.
- /// @param {string} $search Substring.
- /// @param {string} $replace Replacement.
- /// @return {list} Updated list of strings.
- @function str-replace-all($strings, $search, $replace: '') {
-
- @each $string in $strings {
- $strings: set-nth($strings, index($strings, $string), str-replace($string, $search, $replace));
- }
-
- @return $strings;
-
- }
-
- /// Gets a value from a map.
- /// @author Hugo Giraudel
- /// @param {map} $map Map.
- /// @param {string} $keys Key(s).
- /// @return {string} Value.
- @function val($map, $keys...) {
-
- @if nth($keys, 1) == null {
- $keys: remove-nth($keys, 1);
- }
-
- @each $key in $keys {
- $map: map-get($map, $key);
- }
-
- @return $map;
-
- }
-
-// Mixins.
-
- /// Sets the global box model.
- /// @param {string} $model Model (default is content).
- @mixin boxModel($model: 'content') {
-
- $x: $model + '-box';
-
- *, *:before, *:after {
- -moz-box-sizing: #{$x};
- -webkit-box-sizing: #{$x};
- box-sizing: #{$x};
- }
-
- }
-
- /// Wraps @content in a @media block using a given breakpoint.
- /// @param {string} $breakpoint Breakpoint.
- /// @param {map} $queries Additional queries.
- @mixin breakpoint($breakpoint: null, $queries: null) {
-
- $query: 'screen';
-
- // Breakpoint.
- @if $breakpoint and map-has-key($breakpoints, $breakpoint) {
- $query: $query + ' and ' + map-get($breakpoints, $breakpoint);
- }
-
- // Queries.
- @if $queries {
- @each $k, $v in $queries {
- $query: $query + ' and (' + $k + ':' + $v + ')';
- }
- }
-
- @media #{$query} {
- @content;
- }
-
- }
-
- /// Wraps @content in a @media block targeting a specific orientation.
- /// @param {string} $orientation Orientation.
- @mixin orientation($orientation) {
- @media screen and (orientation: #{$orientation}) {
- @content;
- }
- }
-
- /// Utility mixin for containers.
- /// @param {mixed} $width Width.
- @mixin containers($width) {
-
- // Locked?
- $lock: false;
-
- @if length($width) == 2 {
- $width: nth($width, 1);
- $lock: true;
- }
-
- // Modifiers.
- .container.\31 25\25 { width: 100%; max-width: $width * 1.25; min-width: $width; }
- .container.\37 5\25 { width: $width * 0.75; }
- .container.\35 0\25 { width: $width * 0.5; }
- .container.\32 5\25 { width: $width * 0.25; }
-
- // Main class.
- .container {
- @if $lock {
- width: $width !important;
- }
- @else {
- width: $width;
- }
- }
-
- }
-
- /// Utility mixin for grid.
- /// @param {list} $gutters Column and row gutters (default is 40px).
- /// @param {string} $breakpointName Optional breakpoint name.
- @mixin grid($gutters: 40px, $breakpointName: null) {
-
- // Gutters.
- @include grid-gutters($gutters);
- @include grid-gutters($gutters, \32 00\25, 2);
- @include grid-gutters($gutters, \31 50\25, 1.5);
- @include grid-gutters($gutters, \35 0\25, 0.5);
- @include grid-gutters($gutters, \32 5\25, 0.25);
-
- // Cells.
- $x: '';
-
- @if $breakpointName {
- $x: '\\28' + $breakpointName + '\\29';
- }
-
- .\31 2u#{$x}, .\31 2u\24#{$x} { width: 100%; clear: none; margin-left: 0; }
- .\31 1u#{$x}, .\31 1u\24#{$x} { width: 91.6666666667%; clear: none; margin-left: 0; }
- .\31 0u#{$x}, .\31 0u\24#{$x} { width: 83.3333333333%; clear: none; margin-left: 0; }
- .\39 u#{$x}, .\39 u\24#{$x} { width: 75%; clear: none; margin-left: 0; }
- .\38 u#{$x}, .\38 u\24#{$x} { width: 66.6666666667%; clear: none; margin-left: 0; }
- .\37 u#{$x}, .\37 u\24#{$x} { width: 58.3333333333%; clear: none; margin-left: 0; }
- .\36 u#{$x}, .\36 u\24#{$x} { width: 50%; clear: none; margin-left: 0; }
- .\35 u#{$x}, .\35 u\24#{$x} { width: 41.6666666667%; clear: none; margin-left: 0; }
- .\34 u#{$x}, .\34 u\24#{$x} { width: 33.3333333333%; clear: none; margin-left: 0; }
- .\33 u#{$x}, .\33 u\24#{$x} { width: 25%; clear: none; margin-left: 0; }
- .\32 u#{$x}, .\32 u\24#{$x} { width: 16.6666666667%; clear: none; margin-left: 0; }
- .\31 u#{$x}, .\31 u\24#{$x} { width: 8.3333333333%; clear: none; margin-left: 0; }
-
- .\31 2u\24#{$x} + *,
- .\31 1u\24#{$x} + *,
- .\31 0u\24#{$x} + *,
- .\39 u\24#{$x} + *,
- .\38 u\24#{$x} + *,
- .\37 u\24#{$x} + *,
- .\36 u\24#{$x} + *,
- .\35 u\24#{$x} + *,
- .\34 u\24#{$x} + *,
- .\33 u\24#{$x} + *,
- .\32 u\24#{$x} + *,
- .\31 u\24#{$x} + * {
- clear: left;
- }
-
- .\-11u#{$x} { margin-left: 91.6666666667% }
- .\-10u#{$x} { margin-left: 83.3333333333% }
- .\-9u#{$x} { margin-left: 75% }
- .\-8u#{$x} { margin-left: 66.6666666667% }
- .\-7u#{$x} { margin-left: 58.3333333333% }
- .\-6u#{$x} { margin-left: 50% }
- .\-5u#{$x} { margin-left: 41.6666666667% }
- .\-4u#{$x} { margin-left: 33.3333333333% }
- .\-3u#{$x} { margin-left: 25% }
- .\-2u#{$x} { margin-left: 16.6666666667% }
- .\-1u#{$x} { margin-left: 8.3333333333% }
-
- }
-
- /// Utility mixin for grid.
- /// @param {list} $gutters Gutters.
- /// @param {string} $class Optional class name.
- /// @param {integer} $multiplier Multiplier (default is 1).
- @mixin grid-gutters($gutters, $class: null, $multiplier: 1) {
-
- // Expand gutters if it's not a list.
- @if length($gutters) == 1 {
- $gutters: ($gutters, 0);
- }
-
- // Get column and row gutter values.
- $c: nth($gutters, 1);
- $r: nth($gutters, 2);
-
- // Get class (if provided).
- $x: '';
-
- @if $class {
- $x: '.' + $class;
- }
-
- // Default.
- .row#{$x} > * { padding: ($r * $multiplier) 0 0 ($c * $multiplier); }
- .row#{$x} { margin: ($r * $multiplier * -1) 0 -1px ($c * $multiplier * -1); }
-
- // Uniform.
- .row.uniform#{$x} > * { padding: ($c * $multiplier) 0 0 ($c * $multiplier); }
- .row.uniform#{$x} { margin: ($c * $multiplier * -1) 0 -1px ($c * $multiplier * -1); }
-
- }
-
- /// Wraps @content in vendorized keyframe blocks.
- /// @param {string} $name Name.
- @mixin keyframes($name) {
-
- @-moz-keyframes #{$name} { @content; }
- @-webkit-keyframes #{$name} { @content; }
- @-ms-keyframes #{$name} { @content; }
- @keyframes #{$name} { @content; }
-
- }
-
- ///
- /// Sets breakpoints.
- /// @param {map} $x Breakpoints.
- ///
- @mixin skel-breakpoints($x: ()) {
- $breakpoints: $x !global;
- }
-
- ///
- /// Initializes layout module.
- /// @param {map} config Config.
- ///
- @mixin skel-layout($config: ()) {
-
- // Config.
- $configPerBreakpoint: ();
-
- $z: map-get($config, 'breakpoints');
-
- @if $z {
- $configPerBreakpoint: $z;
- }
-
- // Reset.
- $x: map-get($config, 'reset');
-
- @if $x {
-
- /* Reset */
-
- @include reset($x);
-
- }
-
- // Box model.
- $x: map-get($config, 'boxModel');
-
- @if $x {
-
- /* Box Model */
-
- @include boxModel($x);
-
- }
-
- // Containers.
- $containers: map-get($config, 'containers');
-
- @if $containers {
-
- /* Containers */
-
- .container {
- margin-left: auto;
- margin-right: auto;
- }
-
- // Use default is $containers is just "true".
- @if $containers == true {
- $containers: 960px;
- }
-
- // Apply base.
- @include containers($containers);
-
- // Apply per-breakpoint.
- @each $name in map-keys($breakpoints) {
-
- // Get/use breakpoint setting if it exists.
- $x: map-get($configPerBreakpoint, $name);
-
- // Per-breakpoint config exists?
- @if $x {
- $y: map-get($x, 'containers');
-
- // Setting exists? Use it.
- @if $y {
- $containers: $y;
- }
-
- }
-
- // Create @media block.
- @media screen and #{map-get($breakpoints, $name)} {
- @include containers($containers);
- }
-
- }
-
- }
-
- // Grid.
- $grid: map-get($config, 'grid');
-
- @if $grid {
-
- /* Grid */
-
- // Use defaults if $grid is just "true".
- @if $grid == true {
- $grid: ();
- }
-
- // Sub-setting: Gutters.
- $grid-gutters: 40px;
- $x: map-get($grid, 'gutters');
-
- @if $x {
- $grid-gutters: $x;
- }
-
- // Rows.
- .row {
- border-bottom: solid 1px transparent;
- -moz-box-sizing: border-box;
- -webkit-box-sizing: border-box;
- box-sizing: border-box;
- }
-
- .row > * {
- float: left;
- -moz-box-sizing: border-box;
- -webkit-box-sizing: border-box;
- box-sizing: border-box;
- }
-
- .row:after, .row:before {
- content: '';
- display: block;
- clear: both;
- height: 0;
- }
-
- .row.uniform > * > :first-child {
- margin-top: 0;
- }
-
- .row.uniform > * > :last-child {
- margin-bottom: 0;
- }
-
- // Gutters (0%).
- @include grid-gutters($grid-gutters, \30 \25, 0);
-
- // Apply base.
- @include grid($grid-gutters);
-
- // Apply per-breakpoint.
- @each $name in map-keys($breakpoints) {
-
- // Get/use breakpoint setting if it exists.
- $x: map-get($configPerBreakpoint, $name);
-
- // Per-breakpoint config exists?
- @if $x {
- $y: map-get($x, 'grid');
-
- // Setting exists?
- @if $y {
-
- // Sub-setting: Gutters.
- $x: map-get($y, 'gutters');
-
- @if $x {
- $grid-gutters: $x;
- }
-
- }
-
- }
-
- // Create @media block.
- @media screen and #{map-get($breakpoints, $name)} {
- @include grid($grid-gutters, $name);
- }
-
- }
-
- }
-
- }
-
- /// Resets browser styles.
- /// @param {string} $mode Mode (default is 'normalize').
- @mixin reset($mode: 'normalize') {
-
- @if $mode == 'normalize' {
-
- // normalize.css v3.0.2 | MIT License | git.io/normalize
- html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}
-
- }
- @else if $mode == 'full' {
-
- // meyerweb.com/eric/tools/css/reset v2.0 | 20110126 | License: none (public domain)
- html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block;}body{line-height:1;}ol,ul{list-style:none;}blockquote,q{quotes:none;}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}table{border-collapse:collapse;border-spacing:0;}body{-webkit-text-size-adjust:none}
-
- }
-
- }
-
- /// Vendorizes a declaration's property and/or value(s).
- /// @param {string} $property Property.
- /// @param {mixed} $value String/list of value(s).
- @mixin vendor($property, $value) {
-
- // Determine if property should expand.
- $expandProperty: index($vendor-properties, $property);
-
- // Determine if value should expand (and if so, add '-prefix-' placeholder).
- $expandValue: false;
-
- @each $x in $value {
- @each $y in $vendor-values {
- @if $y == str-slice($x, 1, str-length($y)) {
-
- $value: set-nth($value, index($value, $x), '-prefix-' + $x);
- $expandValue: true;
-
- }
- }
- }
-
- // Expand property?
- @if $expandProperty {
- @each $vendor in $vendor-prefixes {
- #{$vendor}#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
- }
- }
-
- // Expand just the value?
- @elseif $expandValue {
- @each $vendor in $vendor-prefixes {
- #{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
- }
- }
-
- // Neither? Treat them as a normal declaration.
- @else {
- #{$property}: #{$value};
- }
-
- }
\ No newline at end of file
diff --git a/assets/sass/libs/_vars.scss b/assets/sass/libs/_vars.scss
deleted file mode 100755
index 828405044..000000000
--- a/assets/sass/libs/_vars.scss
+++ /dev/null
@@ -1,58 +0,0 @@
-// Misc.
- $misc: (
- z-index-base: 10000
- );
-
-// Duration.
- $duration: (
- transition: 0.2s
- );
-
-// Size.
- $size: (
- border-radius: 0.35em,
- element-height: 2.75em,
- element-margin: 2em
- );
-
-// Font.
- $font: (
- family: ('Source Sans Pro', Helvetica, sans-serif),
- family-fixed: ('Courier New', monospace),
- weight: 400,
- weight-bold: 400
- );
-
-// Palette.
- $palette: (
- bg: #fff,
- fg: #a2a2a2,
- fg-bold: #787878,
- fg-light: #b2b2b2,
- border: #efefef,
- border-bg: #f7f7f7,
- border2: #dfdfdf,
- border2-bg: #e7e7e7,
-
- accent1: (
- bg: #49bf9d,
- fg: mix(#49bf9d, #ffffff, 25%),
- fg-bold: #ffffff,
- fg-light: mix(#49bf9d, #ffffff, 40%),
- border: rgba(255,255,255,0.25),
- border-bg: rgba(255,255,255,0.075),
- border2: rgba(255,255,255,0.5),
- border2-bg: rgba(255,255,255,0.2)
- ),
-
- accent2: (
- bg: #1f1815,
- fg: rgba(255,255,255,0.5),
- fg-bold: #ffffff,
- fg-light: rgba(255,255,255,0.4),
- border: rgba(255,255,255,0.25),
- border-bg: rgba(255,255,255,0.075),
- border2: rgba(255,255,255,0.5),
- border2-bg: rgba(255,255,255,0.2)
- )
- );
\ No newline at end of file
diff --git a/assets/sass/main.scss b/assets/sass/main.scss
deleted file mode 100755
index 87e0f2e68..000000000
--- a/assets/sass/main.scss
+++ /dev/null
@@ -1,1492 +0,0 @@
-@import 'libs/vars';
-@import 'libs/functions';
-@import 'libs/mixins';
-@import url('font-awesome.min.css');
-@import url("http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400italic");
-
-/*
- Strata by HTML5 UP
- html5up.net | @ajlkn
- Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-*/
-
- @import "libs/skel";
-
- @include skel-breakpoints((
- xlarge: '(max-width: 1800px)',
- large: '(max-width: 1280px)',
- medium: '(max-width: 980px)',
- small: '(max-width: 736px)',
- xsmall: '(max-width: 480px)'
- ));
-
- @include skel-layout((
- reset: 'full',
- boxModel: 'border',
- grid: ( gutters: 2.5em ),
- conditionals: true,
- containers: true,
- breakpoints: (
- large: (
- grid: (
- gutters: 2em
- ),
- ),
- small: (
- grid: (
- gutters: 1.5em
- ),
- )
- )
- ));
-
- $size-header-width: 35%;
- $size-header-pad: 4em;
-
-/* Basic */
-
- body {
- background: _palette(bg);
-
- &.is-loading {
- *, *:before, *:after {
- @include vendor('animation', 'none !important');
- @include vendor('transition', 'none !important');
- }
- }
- }
-
- body, input, select, textarea {
- color: _palette(fg);
- font-family: _font(family);
- font-size: 16pt;
- font-weight: _font(weight);
- line-height: 1.75em;
- }
-
- a {
- @include vendor('transition', ('color #{_duration(transition)} ease-in-out', 'border-color #{_duration(transition)} ease-in-out'));
- border-bottom: dotted 1px;
- color: _palette(accent1, bg);
- text-decoration: none;
-
- &:hover {
- border-bottom-color: transparent;
- color: _palette(accent1, bg) !important;
- text-decoration: none;
- }
- }
-
- strong, b {
- color: _palette(fg-bold);
- font-weight: _font(weight-bold);
- }
-
- em, i {
- font-style: italic;
- }
-
- p {
- margin: 0 0 _size(element-margin) 0;
- }
-
- h1, h2, h3, h4, h5, h6 {
- color: _palette(fg-bold);
- font-weight: _font(weight-bold);
- line-height: 1em;
- margin: 0 0 (_size(element-margin) * 0.5) 0;
-
- a {
- color: inherit;
- text-decoration: none;
- }
- }
-
- h1 {
- font-size: 2em;
- line-height: 1.5em;
- }
-
- h2 {
- font-size: 1.5em;
- line-height: 1.5em;
- }
-
- h3 {
- font-size: 1.25em;
- line-height: 1.5em;
- }
-
- h4 {
- font-size: 1.1em;
- line-height: 1.5em;
- }
-
- h5 {
- font-size: 0.9em;
- line-height: 1.5em;
- }
-
- h6 {
- font-size: 0.7em;
- line-height: 1.5em;
- }
-
- sub {
- font-size: 0.8em;
- position: relative;
- top: 0.5em;
- }
-
- sup {
- font-size: 0.8em;
- position: relative;
- top: -0.5em;
- }
-
- hr {
- border: 0;
- border-bottom: solid 2px _palette(border);
-
- // This is the *only* instance where we need to rely on margin collapse.
- margin: _size(element-margin) 0;
-
- &.major {
- margin: (_size(element-margin) * 1.5) 0;
- }
- }
-
- blockquote {
- border-left: solid 6px _palette(border);
- font-style: italic;
- margin: 0 0 _size(element-margin) 0;
- padding: 0.5em 0 0.5em 1.5em;
- }
-
- code {
- background: _palette(border-bg);
- border-radius: _size(border-radius);
- border: solid 2px _palette(border);
- font-family: _font(family-fixed);
- font-size: 0.9em;
- margin: 0 0.25em;
- padding: 0.25em 0.65em;
- }
-
- pre {
- -webkit-overflow-scrolling: touch;
- font-family: _font(family-fixed);
- font-size: 0.9em;
- margin: 0 0 _size(element-margin) 0;
-
- code {
- display: block;
- line-height: 1.75em;
- padding: 1em 1.5em;
- overflow-x: auto;
- }
- }
-
- .align-left {
- text-align: left;
- }
-
- .align-center {
- text-align: center;
- }
-
- .align-right {
- text-align: right;
- }
-
-/* Section/Article */
-
- section, article {
- &.special {
- text-align: center;
- }
- }
-
- header {
- p {
- color: _palette(fg-light);
- position: relative;
- margin: 0 0 (_size(element-margin) * 0.75) 0;
- }
-
- h2 + p {
- font-size: 1.25em;
- margin-top: (_size(element-margin) * -0.5);
- line-height: 1.5em;
- }
-
- h3 + p {
- font-size: 1.1em;
- margin-top: (_size(element-margin) * -0.4);
- line-height: 1.5em;
- }
-
- h4 + p,
- h5 + p,
- h6 + p {
- font-size: 0.9em;
- margin-top: (_size(element-margin) * -0.3);
- line-height: 1.5em;
- }
-
- &.major {
- h2 {
- font-size: 2em;
- }
- }
- }
-
-/* Form */
-
- form {
- margin: 0 0 _size(element-margin) 0;
- }
-
- label {
- color: _palette(fg-bold);
- display: block;
- font-size: 0.9em;
- font-weight: _font(weight-bold);
- margin: 0 0 (_size(element-margin) * 0.5) 0;
- }
-
- input[type="text"],
- input[type="password"],
- input[type="email"],
- select,
- textarea {
- @include vendor('appearance', 'none');
- background: _palette(border-bg);
- border-radius: _size(border-radius);
- border: solid 2px transparent;
- color: inherit;
- display: block;
- outline: 0;
- padding: 0 0.75em;
- text-decoration: none;
- width: 100%;
-
- &:invalid {
- box-shadow: none;
- }
-
- &:focus {
- border-color: _palette(accent1, bg);
- }
- }
-
- .select-wrapper {
- @include icon;
- display: block;
- position: relative;
-
- &:before {
- color: _palette(border2);
- content: '\f078';
- display: block;
- height: _size(element-height);
- line-height: _size(element-height);
- pointer-events: none;
- position: absolute;
- right: 0;
- text-align: center;
- top: 0;
- width: _size(element-height);
- }
-
- select::-ms-expand {
- display: none;
- }
- }
-
- input[type="text"],
- input[type="password"],
- input[type="email"],
- select {
- height: _size(element-height);
- }
-
- textarea {
- padding: 0.75em;
- }
-
- input[type="checkbox"],
- input[type="radio"] {
- @include vendor('appearance', 'none');
- display: block;
- float: left;
- margin-right: -2em;
- opacity: 0;
- width: 1em;
- z-index: -1;
-
- & + label {
- @include icon;
- color: _palette(fg);
- cursor: pointer;
- display: inline-block;
- font-size: 1em;
- font-weight: _font(weight);
- padding-left: (_size(element-height) * 0.6) + 0.75em;
- padding-right: 0.75em;
- position: relative;
-
- &:before {
- background: _palette(border-bg);
- border-radius: _size(border-radius);
- border: solid 2px transparent;
- content: '';
- display: inline-block;
- height: (_size(element-height) * 0.6);
- left: 0;
- line-height: (_size(element-height) * 0.575);
- position: absolute;
- text-align: center;
- top: 0;
- width: (_size(element-height) * 0.6);
- }
- }
-
- &:checked + label {
- &:before {
- background: _palette(fg-bold);
- border-color: _palette(fg-bold);
- color: _palette(bg);
- content: '\f00c';
- }
- }
-
- &:focus + label {
- &:before {
- border-color: _palette(accent1, bg);
- }
- }
- }
-
- input[type="checkbox"] {
- & + label {
- &:before {
- border-radius: _size(border-radius);
- }
- }
- }
-
- input[type="radio"] {
- & + label {
- &:before {
- border-radius: 100%;
- }
- }
- }
-
- ::-webkit-input-placeholder {
- color: _palette(fg-light) !important;
- opacity: 1.0;
- }
-
- :-moz-placeholder {
- color: _palette(fg-light) !important;
- opacity: 1.0;
- }
-
- ::-moz-placeholder {
- color: _palette(fg-light) !important;
- opacity: 1.0;
- }
-
- :-ms-input-placeholder {
- color: _palette(fg-light) !important;
- opacity: 1.0;
- }
-
- .formerize-placeholder {
- color: _palette(fg-light) !important;
- opacity: 1.0;
- }
-
-/* Box */
-
- .box {
- border-radius: _size(border-radius);
- border: solid 2px _palette(border);
- margin-bottom: _size(element-margin);
- padding: 1.5em;
-
- > :last-child,
- > :last-child > :last-child,
- > :last-child > :last-child > :last-child {
- margin-bottom: 0;
- }
-
- &.alt {
- border: 0;
- border-radius: 0;
- padding: 0;
- }
- }
-
-/* Icon */
-
- .icon {
- @include icon;
- border-bottom: none;
- position: relative;
-
- > .label {
- display: none;
- }
- }
-
-/* Image */
-
- .image {
- border-radius: _size(border-radius);
- border: 0;
- display: inline-block;
- position: relative;
-
- &:before {
- @include vendor('transition', 'opacity #{_duration(transition)} ease-in-out');
- background: url('images/overlay.png');
- border-radius: _size(border-radius);
- content: '';
- display: block;
- height: 100%;
- left: 0;
- opacity: 0.5;
- position: absolute;
- top: 0;
- width: 100%;
- }
-
- &.thumb {
- text-align: center;
-
- &:after {
- @include vendor('transition', 'opacity #{_duration(transition)} ease-in-out');
- border-radius: _size(border-radius);
- border: solid 3px rgba(255,255,255,0.5);
- color: #fff;
- content: 'Conoce más';
- display: inline-block;
- font-size: 0.8em;
- font-weight: _font(weight-bold);
- left: 50%;
- line-height: 2.25em;
- margin: -1.25em 0 0 -3em;
- opacity: 0;
- padding: 0 1.5em;
- position: absolute;
- text-align: center;
- text-decoration: none;
- top: 50%;
- white-space: nowrap;
- }
-
- &:hover {
- &:after {
- opacity: 1.0;
- }
-
- &:before {
- background: url('images/overlay.png'), url('images/overlay.png');
- opacity: 1.0;
- }
- }
- }
-
- img {
- border-radius: _size(border-radius);
- display: block;
- }
-
- &.left {
- float: left;
- margin: 0 1.5em 1em 0;
- top: 0.25em;
- }
-
- &.right {
- float: right;
- margin: 0 0 1em 1.5em;
- top: 0.25em;
- }
-
- &.left,
- &.right {
- max-width: 40%;
-
- img {
- width: 100%;
- }
- }
-
- &.fit {
- display: block;
- margin: 0 0 _size(element-margin) 0;
- width: 100%;
-
- img {
- width: 100%;
- }
- }
-
- &.avatar {
- border-radius: 100%;
-
- &:before {
- display: none;
- }
-
- img {
- border-radius: 100%;
- width: 100%;
- }
- }
- }
-
-/* List */
-
- ol {
- list-style: decimal;
- margin: 0 0 _size(element-margin) 0;
- padding-left: 1.25em;
-
- li {
- padding-left: 0.25em;
- }
- }
-
- ul {
- list-style: disc;
- margin: 0 0 _size(element-margin) 0;
- padding-left: 1em;
-
- li {
- padding-left: 0.5em;
- }
-
- &.alt {
- list-style: none;
- padding-left: 0;
-
- li {
- border-top: solid 2px _palette(border);
- padding: 0.5em 0;
-
- &:first-child {
- border-top: 0;
- padding-top: 0;
- }
- }
- }
-
- &.icons {
- cursor: default;
- list-style: none;
- padding-left: 0;
-
- li {
- display: inline-block;
- padding: 0 1em 0 0;
-
- &:last-child {
- padding-right: 0;
- }
-
- .icon {
- &:before {
- font-size: 1.5em;
- }
- }
- }
- }
-
- &.actions {
- cursor: default;
- list-style: none;
- padding-left: 0;
-
- li {
- display: inline-block;
- padding: 0 (_size(element-margin) * 0.5) 0 0;
- vertical-align: middle;
-
- &:last-child {
- padding-right: 0;
- }
- }
-
- &.small {
- li {
- padding: 0 (_size(element-margin) * 0.25) 0 0;
- }
- }
-
- &.vertical {
- li {
- display: block;
- padding: (_size(element-margin) * 0.5) 0 0 0;
-
- &:first-child {
- padding-top: 0;
- }
-
- > * {
- margin-bottom: 0;
- }
- }
-
- &.small {
- li {
- padding: (_size(element-margin) * 0.25) 0 0 0;
-
- &:first-child {
- padding-top: 0;
- }
- }
- }
- }
-
- &.fit {
- display: table;
- margin-left: (_size(element-margin) * -0.5);
- padding: 0;
- table-layout: fixed;
- width: calc(100% + #{(_size(element-margin) * 0.5)});
-
- li {
- display: table-cell;
- padding: 0 0 0 (_size(element-margin) * 0.5);
-
- > * {
- margin-bottom: 0;
- }
- }
-
- &.small {
- margin-left: (_size(element-margin) * -0.25);
- width: calc(100% + #{(_size(element-margin) * 0.25)});
-
- li {
- padding: 0 0 0 (_size(element-margin) * 0.25);
- }
- }
- }
- }
-
- &.labeled-icons {
- list-style: none;
- padding: 0;
-
- li {
- line-height: 1.75em;
- margin: 1.5em 0 0 0;
- padding-left: 2.25em;
- position: relative;
-
- &:first-child {
- margin-top: 0;
- }
-
- a {
- color: inherit;
- }
-
- h3 {
- color: _palette(fg-light);
- left: 0;
- position: absolute;
- text-align: center;
- top: 0;
- width: 1em;
- }
- }
- }
- }
-
- dl {
- margin: 0 0 _size(element-margin) 0;
- }
-
-/* Table */
-
- .table-wrapper {
- -webkit-overflow-scrolling: touch;
- overflow-x: auto;
- }
-
- table {
- margin: 0 0 _size(element-margin) 0;
- width: 100%;
-
- tbody {
- tr {
- border: solid 1px _palette(border);
- border-left: 0;
- border-right: 0;
-
- &:nth-child(2n + 1) {
- background-color: _palette(border-bg);
- }
- }
- }
-
- td {
- padding: 0.75em 0.75em;
- }
-
- th {
- color: _palette(fg-bold);
- font-size: 0.9em;
- font-weight: _font(weight-bold);
- padding: 0 0.75em 0.75em 0.75em;
- text-align: left;
- }
-
- thead {
- border-bottom: solid 2px _palette(border);
- }
-
- tfoot {
- border-top: solid 2px _palette(border);
- }
-
- &.alt {
- border-collapse: separate;
-
- tbody {
- tr {
- td {
- border: solid 2px _palette(border);
- border-left-width: 0;
- border-top-width: 0;
-
- &:first-child {
- border-left-width: 2px;
- }
- }
-
- &:first-child {
- td {
- border-top-width: 2px;
- }
- }
- }
- }
-
- thead {
- border-bottom: 0;
- }
-
- tfoot {
- border-top: 0;
- }
- }
- }
-
-/* Button */
-
- input[type="submit"],
- input[type="reset"],
- input[type="button"],
- .button {
- @include vendor('appearance', 'none');
- @include vendor('transition', ('background-color #{_duration(transition)} ease-in-out', 'color #{_duration(transition)} ease-in-out', 'border-color #{_duration(transition)} ease-in-out'));
- background-color: transparent;
- border-radius: _size(border-radius);
- border: solid 3px _palette(border);
- color: _palette(fg-bold) !important;
- cursor: pointer;
- display: inline-block;
- font-weight: _font(weight-bold);
- height: 3.15em;
- height: calc(2.75em + 6px);
- line-height: 2.75em;
- min-width: 10em;
- padding: 0 1.5em;
- text-align: center;
- text-decoration: none;
- white-space: nowrap;
-
- &:hover {
- border-color: _palette(accent1, bg);
- color: _palette(accent1, bg) !important;
- }
-
- &:active {
- background-color: transparentize(_palette(accent1, bg), 0.9);
- border-color: _palette(accent1, bg);
- color: _palette(accent1, bg) !important;
- }
-
- &.icon {
- padding-left: 1.35em;
-
- &:before {
- margin-right: 0.5em;
- }
- }
-
- &.fit {
- display: block;
- margin: 0 0 (_size(element-margin) * 0.5) 0;
- min-width: 0;
- width: 100%;
- }
-
- &.small {
- font-size: 0.8em;
- }
-
- &.big {
- font-size: 1.35em;
- }
-
- &.special {
- background-color: _palette(accent1, bg);
- border-color: _palette(accent1, bg);
- color: _palette(accent1, fg-bold) !important;
-
- &:hover {
- background-color: lighten(_palette(accent1, bg), 5);
- border-color: lighten(_palette(accent1, bg), 5);
- }
-
- &:active {
- background-color: darken(_palette(accent1, bg), 5);
- border-color: darken(_palette(accent1, bg), 5);
- }
- }
-
- &.disabled,
- &:disabled {
- background-color: _palette(border2-bg) !important;
- border-color: _palette(border2-bg) !important;
- color: _palette(fg-light) !important;
- cursor: default;
- }
- }
-
-/* Work Item */
-
- .work-item {
- margin: 0 0 _size(element-margin) 0;
-
- .image {
- margin: 0 0 (_size(element-margin) * 0.75) 0;
- }
-
- h3 {
- font-size: 1em;
- margin: 0 0 (_size(element-margin) * 0.25) 0;
- }
-
- p {
- font-size: 0.8em;
- line-height: 1.5em;
- margin: 0;
- }
- }
-
-/* Header */
-
- #header {
- background-attachment: scroll, fixed;
- background-color: black;
- background-position: top left, top left;
- background-repeat: repeat, no-repeat;
- background-size: auto, auto 100%;
- color: _palette(accent2, fg);
- height: 100%;
- left: 0;
- padding: ($size-header-pad * 2) $size-header-pad 0 0;
- position: fixed;
- text-align: right;
- top: 0;
- width: $size-header-width;
-
- strong, b {
- color: _palette(accent2, fg-bold);
- }
-
- h2, h3, h4, h5, h6 {
- color: _palette(accent2, fg-bold);
- }
-
- h1 {
- color: _palette(accent2, fg);
- font-size: 1.35em;
- line-height: 1.75em;
- margin: 0;
- }
-
- .image.avatar {
- margin: 0 0 (_size(element-margin) * 0.5) 0;
- width: 6.25em;
- }
- }
-
-/* Footer */
-
- #footer {
- bottom: 0;
- left: 0;
- padding: 0 $size-header-pad ($size-header-pad + _size(element-margin)) 0;
- position: fixed;
- text-align: right;
- width: $size-header-width;
-
- .icons {
- margin: (_size(element-margin) * 0.5) 0 0 0;
-
- a {
- color: _palette(accent2, fg-light);
- }
- }
-
- .copyright {
- color: _palette(accent2, fg-light);
- font-size: 0.8em;
- list-style: none;
- margin: (_size(element-margin) * 0.5) 0 0 0;
- padding: 0;
-
- li {
- border-left: solid 1px _palette(accent2, border);
- display: inline-block;
- line-height: 1em;
- margin-left: 0.75em;
- padding-left: 0.75em;
-
- &:first-child {
- border-left: 0;
- margin-left: 0;
- padding-left: 0;
- }
-
- a {
- color: inherit;
- }
- }
- }
- }
-
-/* Main */
-
- #main {
- margin-left: $size-header-width;
- max-width: 50em + $size-header-pad;
- padding: ($size-header-pad * 2) $size-header-pad $size-header-pad $size-header-pad;
- width: calc(100% - #{$size-header-width});
-
- > section {
- border-top: solid 2px _palette(border);
- margin: $size-header-pad 0 0 0;
- padding: $size-header-pad 0 0 0;
-
- &:first-child {
- border-top: 0;
- margin-top: 0;
- padding-top: 0;
- }
- }
- }
-
-/* Poptrox */
-
- @include keyframes('spin') {
- 0% { @include vendor('transform', 'rotate(0deg)'); }
- 100% { @include vendor('transform', 'rotate(360deg)'); }
- };
-
- .poptrox-popup {
- @include vendor('box-sizing', 'content-box');
- -webkit-tap-highlight-color: rgba(255,255,255,0);
- background: #fff;
- border-radius: _size(border-radius);
- box-shadow: 0 0.1em 0.15em 0 rgba(0,0,0,0.15);
- overflow: hidden;
- padding-bottom: 3em;
-
- .loader {
- @include icon;
- @include vendor('animation', 'spin 1s linear infinite');
- font-size: 1.5em;
- height: 1em;
- left: 50%;
- line-height: 1em;
- margin: -0.5em 0 0 -0.5em;
- position: absolute;
- top: 50%;
- width: 1em;
-
- &:before {
- content: '\f1ce';
- }
- }
-
- .caption {
- background: #fff;
- bottom: 0;
- cursor: default;
- font-size: 0.9em;
- height: 3em;
- left: 0;
- line-height: 2.8em;
- position: absolute;
- text-align: center;
- width: 100%;
- z-index: 1;
- }
-
- .nav-next,
- .nav-previous {
- @include icon;
- @include vendor('transition', 'opacity #{_duration(transition)} ease-in-out');
- -webkit-tap-highlight-color: rgba(255,255,255,0);
- background: rgba(0,0,0,0.01);
- cursor: pointer;
- height: 100%;
- opacity: 0;
- position: absolute;
- top: 0;
- width: 50%;
-
- &:before {
- color: #fff;
- font-size: 2.5em;
- height: 1em;
- line-height: 1em;
- margin-top: -0.75em;
- position: absolute;
- text-align: center;
- top: 50%;
- width: 1.5em;
- }
- }
-
- .nav-next {
- right: 0;
-
- &:before {
- content: '\f105';
- right: 0;
- }
- }
-
- .nav-previous {
- left: 0;
-
- &:before {
- content: '\f104';
- left: 0;
- }
- }
-
- .closer {
- @include icon;
- @include vendor('transition', 'opacity #{_duration(transition)} ease-in-out');
- -webkit-tap-highlight-color: rgba(255,255,255,0);
- color: #fff;
- height: 4em;
- line-height: 4em;
- opacity: 0;
- position: absolute;
- right: 0;
- text-align: center;
- top: 0;
- width: 4em;
- z-index: 2;
-
- &:before {
- @include vendor('box-sizing', 'content-box');
- border-radius: 100%;
- border: solid 3px rgba(255,255,255,0.5);
- content: '\f00d';
- display: block;
- font-size: 1em;
- height: 1.75em;
- left: 50%;
- line-height: 1.75em;
- margin: -0.875em 0 0 -0.875em;
- position: absolute;
- top: 50%;
- width: 1.75em;
- }
- }
-
- &:hover {
- .nav-next,
- .nav-previous {
- opacity: 0.5;
-
- &:hover {
- opacity: 1.0;
- }
- }
-
- .closer {
- opacity: 0.5;
-
- &:hover {
- opacity: 1.0;
- }
- }
- }
- }
-
-/* Touch */
-
- body.is-touch {
-
- .image {
- &.thumb {
- &:before {
- opacity: 0.5 !important;
- }
-
- &:after {
- display: none !important;
- }
- }
- }
-
- #header {
- background-attachment: scroll;
- background-size: auto, cover;
- }
-
- .poptrox-popup {
- .nav-next,
- .nav-previous,
- .closer {
- opacity: 1.0 !important;
- }
- }
-
- }
-
-/* XLarge */
-
- @include breakpoint(xlarge) {
-
- /* Basic */
-
- body, input, select, textarea {
- font-size: 12pt;
- }
-
- }
-
-/* Large */
-
- @include breakpoint(large) {
-
- $size-header-width: 30%;
- $size-header-pad: 3em;
-
- /* Header */
-
- #header {
- padding: ($size-header-pad * 2) $size-header-pad $size-header-pad $size-header-pad;
- width: $size-header-width;
-
- h1 {
- font-size: 1.25em;
-
- br {
- display: none;
- }
- }
- }
-
- /* Footer */
-
- #footer {
- padding: 0 $size-header-pad ($size-header-pad + _size(element-margin)) $size-header-pad;
- width: $size-header-width;
-
- .copyright {
- li {
- border-left-width: 0;
- display: block;
- line-height: 2.25em;
- margin-left: 0;
- padding-left: 0;
- }
- }
- }
-
- /* Main */
-
- #main {
- margin-left: $size-header-width;
- max-width: none;
- padding: ($size-header-pad * 2) $size-header-pad $size-header-pad $size-header-pad;
- width: calc(100% - #{$size-header-width});
- }
-
- }
-
-/* Medium */
-
- @include breakpoint(medium) {
-
- $size-header-pad: 4em;
-
- /* Basic */
-
- h1, h2, h3, h4, h5, h6 {
- br {
- display: none;
- }
- }
-
- /* List */
-
- ul {
- &.icons {
- li {
- .icon {
- font-size: 1.25em;
- }
- }
- }
- }
-
- /* Header */
-
- #header {
- background-attachment: scroll;
- background-position: top left, center center;
- background-size: auto, cover;
- left: auto;
- padding: ($size-header-pad * 1.5) $size-header-pad;
- position: relative;
- text-align: center;
- top: auto;
- width: 100%;
-
- h1 {
- font-size: 1.75em;
-
- br {
- display: inline;
- }
- }
- }
-
- /* Footer */
-
- #footer {
- background-attachment: scroll;
- background-color: _palette(accent2, bg);
- background-image: url('images/overlay.png'), url('../../images/bg.jpg');
- background-position: top left, bottom center;
- background-repeat: repeat, no-repeat;
- background-size: auto, cover;
- bottom: auto;
- left: auto;
- padding: $size-header-pad $size-header-pad ($size-header-pad * 1.5) $size-header-pad;
- position: relative;
- text-align: center;
- width: 100%;
-
- .icons {
- margin: 0 0 (_size(element-margin) * 0.5) 0;
- }
-
- .copyright {
- margin: 0 0 (_size(element-margin) * 0.5) 0;
-
- li {
- border-left-width: 1px;
- display: inline-block;
- line-height: 1em;
- margin-left: 0.75em;
- padding-left: 0.75em;
- }
- }
- }
-
- /* Main */
-
- #main {
- margin: 0;
- padding: ($size-header-pad * 1.5) $size-header-pad;
- width: 100%;
- }
-
- }
-
-/* Small */
-
- @include breakpoint(small) {
-
- $size-header-pad: 1.5em;
-
- /* Basic */
-
- h1 {
- font-size: 1.5em;
- }
-
- h2 {
- font-size: 1.2em;
- }
-
- h3 {
- font-size: 1em;
- }
-
- /* Section/Article */
-
- section, article {
- &.special {
- text-align: center;
- }
- }
-
- header {
- &.major {
- h2 {
- font-size: 1.35em;
- }
- }
- }
-
- /* List */
-
- ul {
- &.labeled-icons {
- li {
- padding-left: 2em;
-
- h3 {
- line-height: 1.75em;
- }
- }
- }
- }
-
- /* Header */
-
- #header {
- padding: ($size-header-pad * 1.5) $size-header-pad;
-
- h1 {
- font-size: 1.35em;
- }
- }
-
- /* Footer */
-
- #footer {
- padding: ($size-header-pad * 1.5) $size-header-pad;
- }
-
- /* Main */
-
- #main {
- padding: ($size-header-pad * 1.5) $size-header-pad (($size-header-pad * 1.5) - _size(element-margin)) $size-header-pad;
-
- > section {
- margin: ($size-header-pad * 1.5) 0 0 0;
- padding: ($size-header-pad * 1.5) 0 0 0;
- }
- }
-
- /* Poptrox */
-
- .poptrox-popup {
- border-radius: 0;
-
- .nav-next,
- .nav-previous {
- &:before {
- margin-top: -1em;
- }
- }
- }
-
- }
-
-/* XSmall */
-
- @include breakpoint(xsmall) {
-
- $size-header-pad: 1.5em;
-
- /* List */
-
- ul {
- &.actions {
- margin: 0 0 _size(element-margin) 0;
-
- li {
- display: block;
- padding: (_size(element-margin) * 0.5) 0 0 0;
- text-align: center;
- width: 100%;
-
- &:first-child {
- padding-top: 0;
- }
-
- > * {
- margin: 0 !important;
- width: 100%;
-
- &.icon {
- &:before {
- margin-left: -2em;
- }
- }
- }
- }
-
- &.small {
- li {
- padding: (_size(element-margin) * 0.25) 0 0 0;
-
- &:first-child {
- padding-top: 0;
- }
- }
- }
- }
- }
-
- /* Header */
-
- #header {
- padding: ($size-header-pad * 3) $size-header-pad;
-
- h1 {
- br {
- display: none;
- }
- }
- }
-
- /* Footer */
-
- #footer {
- .copyright {
- li {
- border-left-width: 0;
- display: block;
- line-height: 2.25em;
- margin-left: 0;
- padding-left: 0;
- }
- }
- }
-
- }
diff --git a/images/avatar.jpg b/images/avatar.jpg
deleted file mode 100755
index e7962cc52..000000000
Binary files a/images/avatar.jpg and /dev/null differ
diff --git a/images/bg.jpg b/images/bg.jpg
deleted file mode 100755
index a3bb03c4f..000000000
Binary files a/images/bg.jpg and /dev/null differ
diff --git a/images/fulls/01.jpg b/images/fulls/01.jpg
deleted file mode 100755
index ecb181a2b..000000000
Binary files a/images/fulls/01.jpg and /dev/null differ
diff --git a/images/fulls/02.jpg b/images/fulls/02.jpg
deleted file mode 100755
index d2849ef3e..000000000
Binary files a/images/fulls/02.jpg and /dev/null differ
diff --git a/images/fulls/03.jpg b/images/fulls/03.jpg
deleted file mode 100755
index 2575a1678..000000000
Binary files a/images/fulls/03.jpg and /dev/null differ
diff --git a/images/fulls/04.jpg b/images/fulls/04.jpg
deleted file mode 100755
index d7212bfea..000000000
Binary files a/images/fulls/04.jpg and /dev/null differ
diff --git a/images/fulls/05.jpg b/images/fulls/05.jpg
deleted file mode 100755
index f284385af..000000000
Binary files a/images/fulls/05.jpg and /dev/null differ
diff --git a/images/fulls/06.jpg b/images/fulls/06.jpg
deleted file mode 100755
index cb0e9697c..000000000
Binary files a/images/fulls/06.jpg and /dev/null differ
diff --git a/images/jessicayellow.png b/images/jessicayellow.png
deleted file mode 100755
index 10947392c..000000000
Binary files a/images/jessicayellow.png and /dev/null differ
diff --git a/images/thumbs/01.jpg b/images/thumbs/01.jpg
deleted file mode 100755
index 6444ebe03..000000000
Binary files a/images/thumbs/01.jpg and /dev/null differ
diff --git a/images/thumbs/02.jpg b/images/thumbs/02.jpg
deleted file mode 100755
index 0de4953b7..000000000
Binary files a/images/thumbs/02.jpg and /dev/null differ
diff --git a/images/thumbs/03.jpg b/images/thumbs/03.jpg
deleted file mode 100755
index 33659aae7..000000000
Binary files a/images/thumbs/03.jpg and /dev/null differ
diff --git a/images/thumbs/04.jpg b/images/thumbs/04.jpg
deleted file mode 100755
index 62d9c3715..000000000
Binary files a/images/thumbs/04.jpg and /dev/null differ
diff --git a/images/thumbs/05.jpg b/images/thumbs/05.jpg
deleted file mode 100755
index 27ee8831b..000000000
Binary files a/images/thumbs/05.jpg and /dev/null differ
diff --git a/images/thumbs/06.jpg b/images/thumbs/06.jpg
deleted file mode 100755
index 845aa9bba..000000000
Binary files a/images/thumbs/06.jpg and /dev/null differ
diff --git a/index.html b/index.html
index 59f91042f..6d06a2fb4 100755
--- a/index.html
+++ b/index.html
@@ -1,97 +1,17 @@
-
-
-
-
Jessica Yellow. Growth Hacker.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Tu perfil - Título.
-
-
- A lo largo de mi carrera profesional, siempre me he destacado por crear, compartir y ejecutar campañas de marketing online, principalmente en diferentes corporativos.
-
-
-
-
-
- Mejores experiencias
-
-
-
-
-
-
-
-
- Formo parte de la Comunidad Platzi.
-
- La comunidad profesional web más reconocida del mundo.
-
-
-
-
- Escribo en Medium.
- Escribo tácticas sobre cómo mejorar el marketing de tu empresa.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
Luis Fernando Benavides Rengifo - Software Project Development
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 000000000..43f9e4842
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1666 @@
+{
+ "name": "portfolio-luisfben",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "portfolio-luisfben",
+ "version": "1.0.0",
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.43",
+ "@types/react-dom": "^18.2.17",
+ "@vitejs/plugin-react": "^4.2.1",
+ "vite": "^5.0.8"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz",
+ "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
+ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.4",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.4",
+ "@babel/types": "^7.28.4",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
+ "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.3",
+ "@babel/types": "^7.28.2",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
+ "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.4"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
+ "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.3",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.4",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
+ "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
+ "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz",
+ "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz",
+ "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz",
+ "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz",
+ "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz",
+ "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz",
+ "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz",
+ "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz",
+ "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz",
+ "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz",
+ "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz",
+ "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz",
+ "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz",
+ "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz",
+ "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz",
+ "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz",
+ "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz",
+ "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz",
+ "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz",
+ "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz",
+ "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz",
+ "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.25",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.25.tgz",
+ "integrity": "sha512-oSVZmGtDPmRZtVDqvdKUi/qgCsWp5IDY29wp8na8Bj4B3cc99hfNzvNhlMkVVxctkAOGUA3Km7MMpBHAnWfcIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.10",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz",
+ "integrity": "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.26.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
+ "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.8.9",
+ "caniuse-lite": "^1.0.30001746",
+ "electron-to-chromium": "^1.5.227",
+ "node-releases": "^2.0.21",
+ "update-browserslist-db": "^1.1.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001746",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz",
+ "integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.228",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz",
+ "integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.21",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz",
+ "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.52.3",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz",
+ "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.52.3",
+ "@rollup/rollup-android-arm64": "4.52.3",
+ "@rollup/rollup-darwin-arm64": "4.52.3",
+ "@rollup/rollup-darwin-x64": "4.52.3",
+ "@rollup/rollup-freebsd-arm64": "4.52.3",
+ "@rollup/rollup-freebsd-x64": "4.52.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.52.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.52.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.52.3",
+ "@rollup/rollup-linux-arm64-musl": "4.52.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.52.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.52.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.52.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.52.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.52.3",
+ "@rollup/rollup-linux-x64-gnu": "4.52.3",
+ "@rollup/rollup-linux-x64-musl": "4.52.3",
+ "@rollup/rollup-openharmony-arm64": "4.52.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.52.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.52.3",
+ "@rollup/rollup-win32-x64-gnu": "4.52.3",
+ "@rollup/rollup-win32-x64-msvc": "4.52.3",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.20",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
+ "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..a2d227eb0
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "portfolio-luisfben",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.43",
+ "@types/react-dom": "^18.2.17",
+ "@vitejs/plugin-react": "^4.2.1",
+ "vite": "^5.0.8"
+ }
+}
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 000000000..47e8b56ae
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,10 @@
+
+
+ LFBR
+
+
+
+
+
+
+
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 000000000..152fbcf6d
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,33 @@
+import { ThemeProvider } from './contexts/ThemeContext';
+import { LanguageProvider } from './contexts/LanguageContext';
+import Layout from './components/layout/Layout';
+import Hero from './components/sections/Hero';
+import About from './components/sections/About';
+import TechStack from './components/sections/TechStack';
+import Services from './components/sections/Services';
+import Portfolio from './components/sections/Portfolio';
+import Contact from './components/sections/Contact';
+import './styles/global.css';
+
+/**
+ * Main App Component
+ * Wraps the application with Theme and Language providers
+ */
+function App() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/components/common/Button.jsx b/src/components/common/Button.jsx
new file mode 100644
index 000000000..c21fa738b
--- /dev/null
+++ b/src/components/common/Button.jsx
@@ -0,0 +1,36 @@
+import styles from './Button.module.css';
+
+/**
+ * Button Component - Reusable button with variants
+ * @param {string} variant - 'primary' | 'secondary' | 'outline' | 'ghost'
+ * @param {string} size - 'sm' | 'md' | 'lg'
+ * @param {ReactNode} children - Button content
+ * @param {function} onClick - Click handler
+ * @param {string} className - Additional CSS classes
+ */
+const Button = ({
+ variant = 'primary',
+ size = 'md',
+ children,
+ onClick,
+ className = '',
+ type = 'button',
+ disabled = false,
+ ...props
+}) => {
+ const buttonClass = `${styles.button} ${styles[variant]} ${styles[size]} ${className}`;
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default Button;
diff --git a/src/components/common/Button.module.css b/src/components/common/Button.module.css
new file mode 100644
index 000000000..08c428cf0
--- /dev/null
+++ b/src/components/common/Button.module.css
@@ -0,0 +1,82 @@
+.button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--space-1);
+ font-family: var(--font-primary);
+ font-weight: var(--font-semibold);
+ border-radius: var(--radius-lg);
+ transition: all var(--transition-fast);
+ cursor: pointer;
+ white-space: nowrap;
+ text-decoration: none;
+ border: 2px solid transparent;
+}
+
+.button:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* Sizes */
+.sm {
+ padding: var(--space-1) var(--space-2);
+ font-size: var(--text-sm);
+}
+
+.md {
+ padding: 0.75rem var(--space-3);
+ font-size: var(--text-base);
+}
+
+.lg {
+ padding: var(--space-2) var(--space-4);
+ font-size: var(--text-lg);
+}
+
+/* Variants */
+.primary {
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-2) 100%);
+ color: white;
+ box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3);
+}
+
+.primary:hover:not(:disabled) {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(14, 165, 233, 0.4);
+}
+
+.primary:active:not(:disabled) {
+ transform: translateY(0);
+}
+
+.secondary {
+ background: var(--accent);
+ color: white;
+ box-shadow: 0 4px 12px rgba(167, 139, 250, 0.3);
+}
+
+.secondary:hover:not(:disabled) {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(167, 139, 250, 0.4);
+}
+
+.outline {
+ background: transparent;
+ color: var(--primary);
+ border-color: var(--primary);
+}
+
+.outline:hover:not(:disabled) {
+ background: var(--primary);
+ color: white;
+}
+
+.ghost {
+ background: transparent;
+ color: var(--text);
+}
+
+.ghost:hover:not(:disabled) {
+ background: var(--bg-2);
+}
diff --git a/src/components/common/Card.jsx b/src/components/common/Card.jsx
new file mode 100644
index 000000000..e5d0ef98d
--- /dev/null
+++ b/src/components/common/Card.jsx
@@ -0,0 +1,31 @@
+import styles from './Card.module.css';
+
+/**
+ * Card Component - Reusable card container
+ * @param {ReactNode} children - Card content
+ * @param {string} className - Additional CSS classes
+ * @param {boolean} glass - Apply glassmorphism effect
+ * @param {boolean} hover - Enable hover effect
+ */
+const Card = ({
+ children,
+ className = '',
+ glass = false,
+ hover = false,
+ ...props
+}) => {
+ const cardClass = `
+ ${styles.card}
+ ${glass ? styles.glass : ''}
+ ${hover ? styles.hover : ''}
+ ${className}
+ `.trim();
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default Card;
diff --git a/src/components/common/Card.module.css b/src/components/common/Card.module.css
new file mode 100644
index 000000000..6a4604c84
--- /dev/null
+++ b/src/components/common/Card.module.css
@@ -0,0 +1,24 @@
+.card {
+ background: var(--bg-2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-xl);
+ padding: var(--space-4);
+ transition: all var(--transition-base);
+}
+
+.glass {
+ background: var(--glass-bg);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border: 1px solid var(--glass-border);
+}
+
+.hover {
+ cursor: pointer;
+}
+
+.hover:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 12px 24px var(--shadow-lg);
+ border-color: var(--primary);
+}
diff --git a/src/components/common/LanguageToggle.jsx b/src/components/common/LanguageToggle.jsx
new file mode 100644
index 000000000..b4c1e3e79
--- /dev/null
+++ b/src/components/common/LanguageToggle.jsx
@@ -0,0 +1,28 @@
+import { useLanguage } from '../../contexts/LanguageContext';
+import styles from './LanguageToggle.module.css';
+
+/**
+ * LanguageToggle Component - Switch between ES/EN
+ */
+const LanguageToggle = () => {
+ const { language, toggleLanguage } = useLanguage();
+
+ return (
+
+
+ ES
+
+ /
+
+ EN
+
+
+ );
+};
+
+export default LanguageToggle;
diff --git a/src/components/common/LanguageToggle.module.css b/src/components/common/LanguageToggle.module.css
new file mode 100644
index 000000000..e2b4c1e9f
--- /dev/null
+++ b/src/components/common/LanguageToggle.module.css
@@ -0,0 +1,32 @@
+.toggle {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 0.5rem 1rem;
+ border-radius: var(--radius-lg);
+ background: var(--bg-2);
+ border: 1px solid var(--border);
+ font-family: var(--font-mono);
+ font-size: var(--text-sm);
+ font-weight: var(--font-semibold);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+}
+
+.toggle:hover {
+ border-color: var(--primary);
+ transform: scale(1.05);
+}
+
+.lang {
+ color: var(--text-secondary);
+ transition: color var(--transition-fast);
+}
+
+.lang.active {
+ color: var(--primary);
+}
+
+.separator {
+ color: var(--border);
+}
diff --git a/src/components/common/SectionTitle.jsx b/src/components/common/SectionTitle.jsx
new file mode 100644
index 000000000..16368d679
--- /dev/null
+++ b/src/components/common/SectionTitle.jsx
@@ -0,0 +1,19 @@
+import styles from './SectionTitle.module.css';
+
+/**
+ * SectionTitle Component - Consistent section headers
+ * @param {string} title - Main title
+ * @param {string} subtitle - Optional subtitle
+ * @param {string} align - 'left' | 'center' | 'right'
+ */
+const SectionTitle = ({ title, subtitle, align = 'center' }) => {
+ return (
+
+ {subtitle &&
{subtitle}
}
+
{title}
+
+
+ );
+};
+
+export default SectionTitle;
diff --git a/src/components/common/SectionTitle.module.css b/src/components/common/SectionTitle.module.css
new file mode 100644
index 000000000..e1198809d
--- /dev/null
+++ b/src/components/common/SectionTitle.module.css
@@ -0,0 +1,58 @@
+.sectionTitle {
+ margin-bottom: var(--space-8);
+}
+
+.subtitle {
+ font-size: var(--text-sm);
+ font-weight: var(--font-semibold);
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: var(--primary);
+ margin-bottom: var(--space-2);
+}
+
+.title {
+ font-size: var(--text-4xl);
+ font-weight: var(--font-extrabold);
+ color: var(--text);
+ margin-bottom: var(--space-3);
+}
+
+.divider {
+ width: 60px;
+ height: 4px;
+ background: linear-gradient(90deg, var(--primary), var(--primary-2));
+ border-radius: var(--radius-full);
+}
+
+/* Alignment */
+.left {
+ text-align: left;
+}
+
+.left .divider {
+ margin-left: 0;
+}
+
+.center {
+ text-align: center;
+}
+
+.center .divider {
+ margin: 0 auto;
+}
+
+.right {
+ text-align: right;
+}
+
+.right .divider {
+ margin-right: 0;
+ margin-left: auto;
+}
+
+@media (max-width: 768px) {
+ .title {
+ font-size: var(--text-3xl);
+ }
+}
diff --git a/src/components/common/ThemeToggle.jsx b/src/components/common/ThemeToggle.jsx
new file mode 100644
index 000000000..6b36e3770
--- /dev/null
+++ b/src/components/common/ThemeToggle.jsx
@@ -0,0 +1,38 @@
+import { useTheme } from '../../contexts/ThemeContext';
+import styles from './ThemeToggle.module.css';
+
+/**
+ * ThemeToggle Component - Switch between dark/light themes
+ */
+const ThemeToggle = () => {
+ const { theme, toggleTheme } = useTheme();
+
+ return (
+
+ {theme === 'dark' ? (
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+ )}
+
+ );
+};
+
+export default ThemeToggle;
diff --git a/src/components/common/ThemeToggle.module.css b/src/components/common/ThemeToggle.module.css
new file mode 100644
index 000000000..d635d4b56
--- /dev/null
+++ b/src/components/common/ThemeToggle.module.css
@@ -0,0 +1,28 @@
+.toggle {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ border-radius: var(--radius-lg);
+ background: var(--bg-2);
+ border: 1px solid var(--border);
+ color: var(--text);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+}
+
+.toggle:hover {
+ background: var(--primary);
+ color: white;
+ border-color: var(--primary);
+ transform: scale(1.05);
+}
+
+.toggle svg {
+ transition: transform var(--transition-base);
+}
+
+.toggle:hover svg {
+ transform: rotate(20deg);
+}
diff --git a/src/components/layout/Footer.jsx b/src/components/layout/Footer.jsx
new file mode 100644
index 000000000..2fa2c6f72
--- /dev/null
+++ b/src/components/layout/Footer.jsx
@@ -0,0 +1,109 @@
+import { useLanguage } from '../../contexts/LanguageContext';
+import styles from './Footer.module.css';
+
+/**
+ * Footer Component
+ */
+const Footer = () => {
+ const { t } = useLanguage();
+ const currentYear = new Date().getFullYear();
+
+ const socialLinks = [
+ {
+ name: 'LinkedIn',
+ url: 'https://www.linkedin.com/in/luis-fernando-benavides-rengifo',
+ icon: (
+
+
+
+ )
+ },
+ {
+ name: 'GitHub',
+ url: 'https://github.com/Luisfben',
+ icon: (
+
+
+
+ )
+ },
+ {
+ name: 'Stack Overflow',
+ url: 'https://es.stackoverflow.com/users/114611/luis-fernando-benavides',
+ icon: (
+
+
+
+ )
+ }
+ ];
+
+ return (
+
+
+
+ {/* Logo & Description */}
+
+
+ LFBR
+
+
+ {t('hero.title')}
+
+
+
+ {/* Quick Links */}
+
+
{t('nav.services')}
+
+
+
+ {/* Contact Info */}
+
+
{t('contact.title')}
+
+ {t('contact.info.email')}
+ {t('contact.info.location')}
+ {t('contact.info.availability')}
+
+
+
+ {/* Social Links */}
+
+
+
+ {/* Bottom Bar */}
+
+
+ © {currentYear} Luis Fernando Benavides Rengifo. {t('footer.rights')}.
+
+
+ {t('footer.madeWith')} ♥ {t('footer.by')} Luis Fernando
+
+
+
+
+ );
+};
+
+export default Footer;
diff --git a/src/components/layout/Footer.module.css b/src/components/layout/Footer.module.css
new file mode 100644
index 000000000..f49dfbd81
--- /dev/null
+++ b/src/components/layout/Footer.module.css
@@ -0,0 +1,130 @@
+.footer {
+ background: var(--bg-2);
+ border-top: 1px solid var(--border);
+ padding: var(--space-10) 0 var(--space-4);
+ margin-top: var(--space-12);
+}
+
+.footerContent {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: var(--space-6);
+ margin-bottom: var(--space-8);
+}
+
+.footerSection {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+}
+
+/* Logo */
+.logo {
+ font-family: var(--font-mono);
+ font-size: var(--text-2xl);
+ font-weight: var(--font-bold);
+ margin-bottom: var(--space-2);
+}
+
+.logoText {
+ background: linear-gradient(135deg, var(--primary), var(--primary-2));
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.description {
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+ line-height: 1.6;
+}
+
+/* Section Title */
+.sectionTitle {
+ font-size: var(--text-lg);
+ font-weight: var(--font-semibold);
+ color: var(--text);
+ margin-bottom: var(--space-2);
+}
+
+/* Links */
+.linkList {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-2);
+}
+
+.linkList li {
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+}
+
+.linkList a {
+ color: var(--text-secondary);
+ transition: color var(--transition-fast);
+}
+
+.linkList a:hover {
+ color: var(--primary);
+}
+
+/* Social Links */
+.socialLinks {
+ display: flex;
+ gap: var(--space-2);
+}
+
+.socialLink {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ border-radius: var(--radius-lg);
+ background: var(--bg);
+ border: 1px solid var(--border);
+ color: var(--text-secondary);
+ transition: all var(--transition-fast);
+}
+
+.socialLink:hover {
+ background: var(--primary);
+ color: white;
+ border-color: var(--primary);
+ transform: translateY(-2px);
+}
+
+/* Bottom Bar */
+.footerBottom {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-top: var(--space-4);
+ border-top: 1px solid var(--border);
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+}
+
+.heart {
+ color: #ef4444;
+ animation: heartbeat 1.5s ease-in-out infinite;
+}
+
+@keyframes heartbeat {
+ 0%, 100% { transform: scale(1); }
+ 50% { transform: scale(1.1); }
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .footerContent {
+ grid-template-columns: 1fr;
+ gap: var(--space-4);
+ }
+
+ .footerBottom {
+ flex-direction: column;
+ gap: var(--space-2);
+ text-align: center;
+ }
+}
diff --git a/src/components/layout/Header.jsx b/src/components/layout/Header.jsx
new file mode 100644
index 000000000..4a08a6975
--- /dev/null
+++ b/src/components/layout/Header.jsx
@@ -0,0 +1,105 @@
+import { useState, useEffect } from 'react';
+import { useLanguage } from '../../contexts/LanguageContext';
+import ThemeToggle from '../common/ThemeToggle';
+import LanguageToggle from '../common/LanguageToggle';
+import styles from './Header.module.css';
+
+/**
+ * Header Component - Navigation bar with scroll effect
+ */
+const Header = () => {
+ const { t } = useLanguage();
+ const [isScrolled, setIsScrolled] = useState(false);
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setIsScrolled(window.scrollY > 50);
+ };
+
+ window.addEventListener('scroll', handleScroll);
+ return () => window.removeEventListener('scroll', handleScroll);
+ }, []);
+
+ const scrollToSection = (sectionId) => {
+ const element = document.getElementById(sectionId);
+ if (element) {
+ element.scrollIntoView({ behavior: 'smooth' });
+ setIsMobileMenuOpen(false);
+ }
+ };
+
+ const navItems = [
+ { key: 'about', id: 'about' },
+ { key: 'services', id: 'services' },
+ { key: 'portfolio', id: 'portfolio' },
+ { key: 'contact', id: 'contact' },
+ ];
+
+ return (
+
+
+ {/* Logo */}
+
window.scrollTo({ top: 0, behavior: 'smooth' })}>
+ LFBR
+
+
+ {/* Desktop Navigation */}
+
+ {navItems.map((item) => (
+ scrollToSection(item.id)}
+ >
+ {t(`nav.${item.key}`)}
+
+ ))}
+
+
+ {/* Controls */}
+
+
+
+
+ {/* Mobile Menu Button */}
+ setIsMobileMenuOpen(!isMobileMenuOpen)}
+ aria-label="Toggle menu"
+ >
+ {isMobileMenuOpen ? (
+
+
+
+
+ ) : (
+
+
+
+
+
+ )}
+
+
+
+
+ {/* Mobile Menu */}
+ {isMobileMenuOpen && (
+
+ {navItems.map((item) => (
+ scrollToSection(item.id)}
+ >
+ {t(`nav.${item.key}`)}
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default Header;
diff --git a/src/components/layout/Header.module.css b/src/components/layout/Header.module.css
new file mode 100644
index 000000000..d0a1463c2
--- /dev/null
+++ b/src/components/layout/Header.module.css
@@ -0,0 +1,167 @@
+.header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: var(--z-fixed);
+ background: var(--bg);
+ border-bottom: 1px solid transparent;
+ transition: all var(--transition-base);
+}
+
+.header.scrolled {
+ background: var(--glass-bg);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border-bottom-color: var(--border);
+ box-shadow: 0 4px 12px var(--shadow);
+}
+
+.headerContent {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 80px;
+}
+
+/* Logo */
+.logo {
+ font-family: var(--font-mono);
+ font-size: var(--text-xl);
+ font-weight: var(--font-bold);
+ color: var(--text);
+ cursor: pointer;
+ transition: color var(--transition-fast);
+}
+
+.logo:hover {
+ color: var(--primary);
+}
+
+.logoText {
+ background: linear-gradient(135deg, var(--primary), var(--primary-2));
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+/* Navigation */
+.nav {
+ display: flex;
+ align-items: center;
+ gap: var(--space-4);
+}
+
+.navLink {
+ font-size: var(--text-base);
+ font-weight: var(--font-medium);
+ color: var(--text-secondary);
+ background: none;
+ border: none;
+ cursor: pointer;
+ transition: color var(--transition-fast);
+ position: relative;
+}
+
+.navLink::after {
+ content: '';
+ position: absolute;
+ bottom: -4px;
+ left: 0;
+ width: 0;
+ height: 2px;
+ background: var(--primary);
+ transition: width var(--transition-fast);
+}
+
+.navLink:hover {
+ color: var(--text);
+}
+
+.navLink:hover::after {
+ width: 100%;
+}
+
+/* Controls */
+.controls {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+}
+
+/* Mobile Menu Button */
+.mobileMenuButton {
+ display: none;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ border-radius: var(--radius-lg);
+ background: var(--bg-2);
+ border: 1px solid var(--border);
+ color: var(--text);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+}
+
+.mobileMenuButton:hover {
+ background: var(--primary);
+ color: white;
+ border-color: var(--primary);
+}
+
+/* Mobile Menu */
+.mobileMenu {
+ display: none;
+ flex-direction: column;
+ padding: var(--space-4);
+ background: var(--bg-2);
+ border-top: 1px solid var(--border);
+ animation: slideDown 0.3s ease-out;
+}
+
+@keyframes slideDown {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.mobileNavLink {
+ padding: var(--space-2);
+ font-size: var(--text-lg);
+ font-weight: var(--font-medium);
+ color: var(--text-secondary);
+ background: none;
+ border: none;
+ cursor: pointer;
+ text-align: left;
+ transition: color var(--transition-fast);
+}
+
+.mobileNavLink:hover {
+ color: var(--primary);
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .nav {
+ display: none;
+ }
+
+ .mobileMenuButton {
+ display: flex;
+ }
+
+ .mobileMenu {
+ display: flex;
+ }
+
+ .headerContent {
+ height: 70px;
+ }
+}
diff --git a/src/components/layout/Layout.jsx b/src/components/layout/Layout.jsx
new file mode 100644
index 000000000..412b6243e
--- /dev/null
+++ b/src/components/layout/Layout.jsx
@@ -0,0 +1,19 @@
+import Header from './Header';
+import Footer from './Footer';
+
+/**
+ * Layout Component - Main layout wrapper
+ */
+const Layout = ({ children }) => {
+ return (
+ <>
+
+
+ {children}
+
+
+ >
+ );
+};
+
+export default Layout;
diff --git a/src/components/sections/About.jsx b/src/components/sections/About.jsx
new file mode 100644
index 000000000..a62982303
--- /dev/null
+++ b/src/components/sections/About.jsx
@@ -0,0 +1,132 @@
+import { useLanguage } from '../../contexts/LanguageContext';
+import SectionTitle from '../common/SectionTitle';
+import Card from '../common/Card';
+import styles from './About.module.css';
+
+/**
+ * About Section - Professional background and expertise
+ */
+const About = () => {
+ const { t } = useLanguage();
+
+ // Calculate number of projects dynamically from translations
+ const projectsCount = Object.keys(t('portfolio.projects')).length;
+
+ // Calculate unique sectors from projects
+ const calculateSectors = () => {
+ try {
+ const projects = t('portfolio.projects');
+ if (!projects || typeof projects !== 'object') return 6;
+
+ const sectors = new Set();
+
+ Object.values(projects).forEach(project => {
+ if (!project || !project.impact) return;
+ const impact = project.impact.toLowerCase();
+
+ if (impact.includes('salud') || impact.includes('health') || impact.includes('eps')) {
+ sectors.add('health');
+ }
+ if (impact.includes('educación') || impact.includes('education') || impact.includes('puj')) {
+ sectors.add('education');
+ }
+ if (impact.includes('industrial') || impact.includes('manufactur')) {
+ sectors.add('industrial');
+ }
+ if (impact.includes('comercial') || impact.includes('commercial') || impact.includes('finance')) {
+ sectors.add('commercial');
+ }
+ if (impact.includes('público') || impact.includes('public') || impact.includes('government')) {
+ sectors.add('public');
+ }
+ if (impact.includes('cloud') || impact.includes('aws') || impact.includes('devops') || impact.includes('ci/cd')) {
+ sectors.add('technology');
+ }
+ });
+
+ return sectors.size || 6;
+ } catch (error) {
+ console.error('Error calculating sectors:', error);
+ return 6; // Fallback value
+ }
+ };
+
+ const sectorsCount = calculateSectors();
+
+ const expertiseItems = [
+ t('about.expertise.item1'),
+ t('about.expertise.item2'),
+ t('about.expertise.item3'),
+ t('about.expertise.item4'),
+ t('about.expertise.item5'),
+ t('about.expertise.item6'),
+ ];
+
+ return (
+
+
+
+
+
+ {/* Introduction */}
+
+
+ {/* Education & Experience Cards */}
+
+
+
+ {t('about.education.title')}
+ {t('about.education.degree')}
+ {t('about.education.institution')}
+
+
+
+
+ {t('about.experience.title')}
+
+ {t('about.experience.years')} {t('about.experience.yearsLabel')}
+
+
+ {projectsCount} {t('about.experience.projectsLabel')} • {sectorsCount} {t('about.experience.sectorsLabel')}
+
+
+
+
+ {/* Expertise Grid */}
+
+
{t('about.expertise.title')}
+
+ {expertiseItems.map((item, index) => (
+
+ ))}
+
+
+
+
+
+ );
+};
+
+export default About;
diff --git a/src/components/sections/About.module.css b/src/components/sections/About.module.css
new file mode 100644
index 000000000..946c51cb1
--- /dev/null
+++ b/src/components/sections/About.module.css
@@ -0,0 +1,145 @@
+.about {
+ background: var(--bg);
+}
+
+.content {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-8);
+}
+
+/* Introduction */
+.intro {
+ max-width: 800px;
+ margin: 0 auto;
+ text-align: center;
+}
+
+.introText {
+ font-size: var(--text-lg);
+ color: var(--text-secondary);
+ line-height: 1.8;
+}
+
+/* Cards */
+.cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: var(--space-4);
+}
+
+.card {
+ text-align: center;
+ transition: all var(--transition-base);
+}
+
+.card:hover {
+ transform: translateY(-4px);
+ border-color: var(--primary);
+}
+
+.cardIcon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 64px;
+ height: 64px;
+ border-radius: var(--radius-xl);
+ background: linear-gradient(135deg, var(--primary), var(--primary-2));
+ color: white;
+ margin-bottom: var(--space-3);
+}
+
+.cardTitle {
+ font-size: var(--text-2xl);
+ font-weight: var(--font-bold);
+ color: var(--text);
+ margin-bottom: var(--space-2);
+}
+
+.cardText {
+ font-size: var(--text-lg);
+ color: var(--text-secondary);
+ margin-bottom: var(--space-1);
+}
+
+.cardSubtext {
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+ opacity: 0.8;
+}
+
+.highlight {
+ font-family: var(--font-mono);
+ font-size: var(--text-3xl);
+ font-weight: var(--font-bold);
+ background: linear-gradient(135deg, var(--primary), var(--accent));
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ display: block;
+ margin-bottom: var(--space-1);
+}
+
+/* Expertise */
+.expertise {
+ background: var(--bg-2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-2xl);
+ padding: var(--space-6);
+}
+
+.expertiseTitle {
+ font-size: var(--text-2xl);
+ font-weight: var(--font-bold);
+ color: var(--text);
+ text-align: center;
+ margin-bottom: var(--space-6);
+}
+
+.expertiseGrid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: var(--space-3);
+}
+
+.expertiseItem {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ padding: var(--space-2);
+ border-radius: var(--radius-lg);
+ background: var(--bg);
+ border: 1px solid var(--border);
+ transition: all var(--transition-fast);
+ font-size: var(--text-base);
+ color: var(--text-secondary);
+}
+
+.expertiseItem:hover {
+ border-color: var(--primary);
+ transform: translateX(4px);
+}
+
+.expertiseIcon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border-radius: var(--radius-md);
+ background: var(--primary);
+ color: white;
+ flex-shrink: 0;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .cards {
+ grid-template-columns: 1fr;
+ }
+
+ .expertiseGrid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/components/sections/Contact.jsx b/src/components/sections/Contact.jsx
new file mode 100644
index 000000000..d919c016a
--- /dev/null
+++ b/src/components/sections/Contact.jsx
@@ -0,0 +1,268 @@
+import { useState } from 'react';
+import { useLanguage } from '../../contexts/LanguageContext';
+import SectionTitle from '../common/SectionTitle';
+import Button from '../common/Button';
+import Card from '../common/Card';
+import styles from './Contact.module.css';
+
+/**
+ * Contact Section - Contact form and information
+ */
+const Contact = () => {
+ const { t } = useLanguage();
+ const [formData, setFormData] = useState({
+ name: '',
+ email: '',
+ subject: '',
+ message: '',
+ });
+ const [status, setStatus] = useState('idle'); // idle | sending | success | error
+
+ const handleChange = (e) => {
+ setFormData({
+ ...formData,
+ [e.target.name]: e.target.value,
+ });
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setStatus('sending');
+
+ try {
+ // Using FormSubmit.co - Free form backend service
+ const response = await fetch('https://formsubmit.co/lfbenavides@gmail.com', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'
+ },
+ body: JSON.stringify({
+ name: formData.name,
+ email: formData.email,
+ subject: formData.subject,
+ message: formData.message,
+ _captcha: 'false', // Disable captcha
+ _template: 'table', // Use table template for email
+ })
+ });
+
+ if (response.ok) {
+ setStatus('success');
+ setFormData({ name: '', email: '', subject: '', message: '' });
+ setTimeout(() => setStatus('idle'), 5000);
+ } else {
+ setStatus('error');
+ setTimeout(() => setStatus('idle'), 5000);
+ }
+ } catch (error) {
+ console.error('Error sending form:', error);
+ setStatus('error');
+ setTimeout(() => setStatus('idle'), 5000);
+ }
+ };
+
+ const contactInfo = [
+ {
+ icon: (
+
+
+
+
+ ),
+ label: 'Email',
+ value: t('contact.info.email'),
+ link: `mailto:${t('contact.info.email')}`,
+ },
+ {
+ icon: (
+
+
+
+
+ ),
+ label: 'Location',
+ value: t('contact.info.location'),
+ },
+ {
+ icon: (
+
+
+
+
+ ),
+ label: 'Availability',
+ value: t('contact.info.availability'),
+ },
+ ];
+
+ return (
+
+ );
+};
+
+export default Contact;
diff --git a/src/components/sections/Contact.module.css b/src/components/sections/Contact.module.css
new file mode 100644
index 000000000..5acea13a2
--- /dev/null
+++ b/src/components/sections/Contact.module.css
@@ -0,0 +1,196 @@
+.contact {
+ background: var(--bg);
+}
+
+.description {
+ max-width: 600px;
+ margin: 0 auto var(--space-8);
+ text-align: center;
+ font-size: var(--text-lg);
+ color: var(--text-secondary);
+}
+
+.contactContent {
+ display: grid;
+ grid-template-columns: 1.5fr 1fr;
+ gap: var(--space-8);
+ align-items: start;
+}
+
+/* Form */
+.formCard {
+ animation: fadeIn 0.6s ease-out;
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-4);
+}
+
+.formGroup {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-1);
+}
+
+.label {
+ font-size: var(--text-sm);
+ font-weight: var(--font-semibold);
+ color: var(--text);
+}
+
+.input,
+.textarea {
+ padding: var(--space-2) var(--space-3);
+ font-size: var(--text-base);
+ color: var(--text);
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ transition: all var(--transition-fast);
+}
+
+.input:focus,
+.textarea:focus {
+ outline: none;
+ border-color: var(--primary);
+ box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
+}
+
+.textarea {
+ resize: vertical;
+ min-height: 120px;
+ font-family: inherit;
+}
+
+.submitButton {
+ width: 100%;
+}
+
+.successMessage {
+ padding: var(--space-3);
+ background: rgba(34, 197, 94, 0.1);
+ border: 1px solid rgba(34, 197, 94, 0.3);
+ border-radius: var(--radius-lg);
+ color: #22c55e;
+ text-align: center;
+ font-weight: var(--font-medium);
+}
+
+.errorMessage {
+ padding: var(--space-3);
+ background: rgba(239, 68, 68, 0.1);
+ border: 1px solid rgba(239, 68, 68, 0.3);
+ border-radius: var(--radius-lg);
+ color: #ef4444;
+ text-align: center;
+ font-weight: var(--font-medium);
+}
+
+/* Info Section */
+.infoSection {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-4);
+ animation: fadeIn 0.6s ease-out 0.2s backwards;
+}
+
+.infoCard {
+ display: flex;
+ align-items: center;
+ gap: var(--space-3);
+ transition: all var(--transition-base);
+}
+
+.infoCard:hover {
+ transform: translateX(4px);
+ border-color: var(--primary);
+}
+
+.infoIcon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 48px;
+ height: 48px;
+ border-radius: var(--radius-lg);
+ background: var(--primary);
+ color: white;
+ flex-shrink: 0;
+}
+
+.infoContent {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.infoLabel {
+ font-size: var(--text-xs);
+ font-weight: var(--font-semibold);
+ color: var(--text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.infoValue {
+ font-size: var(--text-base);
+ color: var(--text);
+ font-weight: var(--font-medium);
+}
+
+.infoValue:hover {
+ color: var(--primary);
+}
+
+/* Social Card */
+.socialCard {
+ padding: var(--space-4);
+}
+
+.socialTitle {
+ font-size: var(--text-lg);
+ font-weight: var(--font-bold);
+ color: var(--text);
+ margin-bottom: var(--space-3);
+}
+
+.socialLinks {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-2);
+}
+
+.socialLink {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ padding: var(--space-2);
+ border-radius: var(--radius-lg);
+ background: var(--bg);
+ border: 1px solid var(--border);
+ color: var(--text-secondary);
+ font-weight: var(--font-medium);
+ transition: all var(--transition-fast);
+}
+
+.socialLink:hover {
+ background: var(--primary);
+ color: white;
+ border-color: var(--primary);
+ transform: translateX(4px);
+}
+
+/* Responsive */
+@media (max-width: 1024px) {
+ .contactContent {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 768px) {
+ .description {
+ font-size: var(--text-base);
+ }
+}
diff --git a/src/components/sections/Hero.jsx b/src/components/sections/Hero.jsx
new file mode 100644
index 000000000..8f96f9dab
--- /dev/null
+++ b/src/components/sections/Hero.jsx
@@ -0,0 +1,116 @@
+import { useLanguage } from '../../contexts/LanguageContext';
+import Button from '../common/Button';
+import Card from '../common/Card';
+import styles from './Hero.module.css';
+
+/**
+ * Hero Section - Landing section with main CTA
+ */
+const Hero = () => {
+ const { t, language } = useLanguage();
+
+ // Calculate number of projects dynamically from translations
+ const projectsCount = Object.keys(t('portfolio.projects')).length;
+
+ // Calculate unique sectors from projects
+ const calculateSectors = () => {
+ try {
+ const projects = t('portfolio.projects');
+ if (!projects || typeof projects !== 'object') return 6;
+
+ const sectors = new Set();
+
+ Object.values(projects).forEach(project => {
+ if (!project || !project.impact) return;
+ const impact = project.impact.toLowerCase();
+
+ if (impact.includes('salud') || impact.includes('health') || impact.includes('eps')) {
+ sectors.add('health');
+ }
+ if (impact.includes('educación') || impact.includes('education') || impact.includes('puj')) {
+ sectors.add('education');
+ }
+ if (impact.includes('industrial') || impact.includes('manufactur')) {
+ sectors.add('industrial');
+ }
+ if (impact.includes('comercial') || impact.includes('commercial') || impact.includes('finance')) {
+ sectors.add('commercial');
+ }
+ if (impact.includes('público') || impact.includes('public') || impact.includes('government')) {
+ sectors.add('public');
+ }
+ if (impact.includes('cloud') || impact.includes('aws') || impact.includes('devops') || impact.includes('ci/cd')) {
+ sectors.add('technology');
+ }
+ });
+
+ return sectors.size || 6;
+ } catch (error) {
+ console.error('Error calculating sectors:', error);
+ return 6; // Fallback value
+ }
+ };
+
+ const sectorsCount = calculateSectors();
+
+ const scrollToContact = () => {
+ document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ const scrollToPortfolio = () => {
+ document.getElementById('portfolio')?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ return (
+
+
+
+ {/* Main Content */}
+
+
{t('hero.greeting')}
+
+ {t('hero.title')}
+
+ {t('hero.titleHighlight')}
+
+
{t('hero.subtitle')}
+
+
+
+ {t('hero.cta')}
+
+
+ {t('hero.ctaSecondary')}
+
+
+
+
+ {/* Stats Card */}
+
+
+ {t('about.experience.years')}
+ {t('about.experience.yearsLabel')}
+
+
+ {projectsCount}
+ {t('about.experience.projectsLabel')}
+
+
+ {sectorsCount}
+ {t('about.experience.sectorsLabel')}
+
+
+
+
+ {/* Scroll Indicator */}
+
+
+
+ );
+};
+
+export default Hero;
diff --git a/src/components/sections/Hero.module.css b/src/components/sections/Hero.module.css
new file mode 100644
index 000000000..657841540
--- /dev/null
+++ b/src/components/sections/Hero.module.css
@@ -0,0 +1,192 @@
+.hero {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+}
+
+.hero::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: radial-gradient(circle at 20% 50%, rgba(14, 165, 233, 0.1) 0%, transparent 50%),
+ radial-gradient(circle at 80% 80%, rgba(167, 139, 250, 0.1) 0%, transparent 50%);
+ pointer-events: none;
+}
+
+.heroContent {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: var(--space-8);
+ align-items: center;
+ position: relative;
+ z-index: 1;
+}
+
+.textContent {
+ animation: fadeIn 0.8s ease-out;
+}
+
+.greeting {
+ font-size: var(--text-lg);
+ font-weight: var(--font-medium);
+ color: var(--primary);
+ margin-bottom: var(--space-2);
+}
+
+.title {
+ font-size: var(--text-6xl);
+ font-weight: var(--font-extrabold);
+ line-height: 1.2;
+ margin-bottom: var(--space-3);
+}
+
+.titleHighlight {
+ font-size: var(--text-2xl);
+ font-weight: var(--font-semibold);
+ color: var(--text-secondary);
+ display: block;
+ margin-top: var(--space-2);
+}
+
+.subtitle {
+ font-size: var(--text-xl);
+ color: var(--text-secondary);
+ line-height: 1.6;
+ margin-bottom: var(--space-6);
+}
+
+.cta {
+ display: flex;
+ gap: var(--space-3);
+ flex-wrap: wrap;
+}
+
+/* Stats Card */
+.statsCard {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: var(--space-4);
+ animation: slideInRight 0.8s ease-out 0.2s backwards;
+}
+
+.stat {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ padding: var(--space-3);
+ border-radius: var(--radius-lg);
+ background: var(--bg-2);
+ border: 1px solid var(--border);
+ transition: all var(--transition-base);
+}
+
+.stat:hover {
+ transform: translateY(-4px);
+ border-color: var(--primary);
+}
+
+.statNumber {
+ font-family: var(--font-mono);
+ font-size: var(--text-4xl);
+ font-weight: var(--font-bold);
+ background: linear-gradient(135deg, var(--primary), var(--primary-2));
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ margin-bottom: var(--space-1);
+}
+
+.statLabel {
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+}
+
+/* Scroll Indicator */
+.scrollIndicator {
+ position: absolute;
+ bottom: var(--space-6);
+ left: 50%;
+ transform: translateX(-50%);
+ animation: bounce 2s infinite;
+}
+
+.mouse {
+ width: 24px;
+ height: 40px;
+ border: 2px solid var(--text-secondary);
+ border-radius: 12px;
+ position: relative;
+}
+
+.wheel {
+ width: 4px;
+ height: 8px;
+ background: var(--primary);
+ border-radius: 2px;
+ position: absolute;
+ top: 8px;
+ left: 50%;
+ transform: translateX(-50%);
+ animation: scroll 1.5s infinite;
+}
+
+@keyframes bounce {
+ 0%, 100% {
+ transform: translateX(-50%) translateY(0);
+ }
+ 50% {
+ transform: translateX(-50%) translateY(-10px);
+ }
+}
+
+@keyframes scroll {
+ 0% {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+ }
+ 100% {
+ opacity: 0;
+ transform: translateX(-50%) translateY(16px);
+ }
+}
+
+/* Responsive */
+@media (max-width: 1024px) {
+ .heroContent {
+ grid-template-columns: 1fr;
+ gap: var(--space-6);
+ }
+
+ .statsCard {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
+@media (max-width: 768px) {
+ .hero {
+ min-height: calc(100vh - 70px);
+ }
+
+ .title {
+ font-size: var(--text-4xl);
+ }
+
+ .subtitle {
+ font-size: var(--text-lg);
+ }
+
+ .cta {
+ flex-direction: column;
+ }
+
+ .statsCard {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/components/sections/Portfolio.jsx b/src/components/sections/Portfolio.jsx
new file mode 100644
index 000000000..6909d921a
--- /dev/null
+++ b/src/components/sections/Portfolio.jsx
@@ -0,0 +1,285 @@
+import { useState } from 'react';
+import { useLanguage } from '../../contexts/LanguageContext';
+import SectionTitle from '../common/SectionTitle';
+import Card from '../common/Card';
+import styles from './Portfolio.module.css';
+
+/**
+ * Portfolio Section - Featured projects showcase with category tabs
+ */
+const Portfolio = () => {
+ const { t } = useLanguage();
+ const [activeTab, setActiveTab] = useState('all');
+
+ const projects = [
+ {
+ key: 'lowcode',
+ category: 'education',
+ tech: ['Low-Code', 'Research', 'Architecture', 'PUJ'],
+ gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2020/01/estudio-de-factibilidad-para-la.html',
+ featured: true
+ },
+ {
+ key: 'freedcamp',
+ category: 'fintech',
+ tech: ['Spring Boot', 'Java', 'REST API', 'Freedcamp'],
+ gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2020/01/implementacion-proyecto-web-con.html',
+ featured: true
+ },
+ {
+ key: 'apirest',
+ category: 'fintech',
+ tech: ['Spring Boot', 'Java', 'REST API', 'Backend'],
+ gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/12/implementacion-proyecto-api-rest-con.html',
+ featured: true
+ },
+ {
+ key: 'bootstrap',
+ category: 'fintech',
+ tech: ['Spring Boot', 'Bootstrap', 'Java', 'Web'],
+ gradient: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/11/implementacion-proyecto-web-con.html',
+ featured: false
+ },
+ {
+ key: 'lambda',
+ category: 'cloud',
+ tech: ['AWS Lambda', 'Serverless', 'REST API', 'Cloud'],
+ gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/11/implementacion-de-servicios-api-rest.html',
+ featured: true
+ },
+ {
+ key: 'sms',
+ category: 'health',
+ tech: ['SMS', 'Java', 'Healthcare', 'EPS'],
+ gradient: 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/08/implementacion-de-envio-de-mensajes-sms.html',
+ featured: false
+ },
+ {
+ key: 'ci',
+ category: 'cloud',
+ tech: ['CI/CD', 'Testing', 'DevOps', 'Jenkins'],
+ gradient: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/01/implementacion-de-integracion-continua.html',
+ featured: true
+ },
+ {
+ key: 'patrones',
+ category: 'cloud',
+ tech: ['Design Patterns', 'Architecture', 'Java', 'OOP'],
+ gradient: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/01/implementacion-de-patrones-de-diseno-un.html',
+ featured: false
+ },
+ {
+ key: 'bolsa',
+ category: 'education',
+ tech: ['PeopleSoft', 'Integration', 'Java', 'Education'],
+ gradient: 'linear-gradient(135deg, #fbc2eb 0%, #a6c1ee 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/01/proyecto-que-integra-la-bolsa-de-empleo.html',
+ featured: false
+ },
+ {
+ key: 'salesforce',
+ category: 'education',
+ tech: ['Salesforce', 'CRM', 'PeopleSoft', 'Integration'],
+ gradient: 'linear-gradient(135deg, #fdcbf1 0%, #e6dee9 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/01/proyecto-que-integra-crm-sales-force.html',
+ featured: true
+ },
+ {
+ key: 'mega',
+ category: 'health',
+ tech: ['Healthcare', 'EPS', 'Java', 'Enterprise'],
+ gradient: 'linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/01/mejoramiento-en-la-gestion-de-las.html',
+ featured: true
+ },
+ {
+ key: 'fosyga',
+ category: 'health',
+ tech: ['Healthcare', 'Legal', 'Java', 'Compliance'],
+ gradient: 'linear-gradient(135deg, #d299c2 0%, #fef9d7 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/01/desarrollo-e-implementacion-de-software.html',
+ featured: false
+ },
+ {
+ key: 'arco',
+ category: 'others',
+ tech: ['Manufacturing', 'Java', 'FoxPro', 'Industrial'],
+ gradient: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-desarrollo-e-implementacion-de.html',
+ featured: false
+ },
+ {
+ key: 'tarjeta',
+ category: 'others',
+ tech: ['Finance', 'Java', 'FoxPro', 'Commercial'],
+ gradient: 'linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-desarrollo-y-implementacion-de.html',
+ featured: false
+ },
+ {
+ key: 'softcontrol',
+ category: 'others',
+ tech: ['Government', 'Java', 'FoxPro', 'Public Sector'],
+ gradient: 'linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-y-desarrollo-de-un-aplicativo.html',
+ featured: false
+ },
+ {
+ key: 'inventarios',
+ category: 'others',
+ tech: ['Inventory', 'Java', 'FoxPro', 'Commercial'],
+ gradient: 'linear-gradient(135deg, #81c784 0%, #aed581 100%)',
+ link: 'https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-y-desarrollo-de-software-para-el.html',
+ featured: false
+ },
+ ];
+
+ const tabs = [
+ { key: 'all', label: t('portfolio.tabs.all') },
+ { key: 'fintech', label: t('portfolio.tabs.fintech') },
+ { key: 'cloud', label: t('portfolio.tabs.cloud') },
+ { key: 'education', label: t('portfolio.tabs.education') },
+ { key: 'health', label: t('portfolio.tabs.health') },
+ { key: 'others', label: t('portfolio.tabs.others') },
+ ];
+
+ const filteredProjects = activeTab === 'all'
+ ? projects.filter(p => p.featured)
+ : projects.filter(p => p.category === activeTab);
+
+ // Category-specific icons
+ const getCategoryIcon = (category) => {
+ const icons = {
+ fintech: (
+
+
+
+
+ ),
+ cloud: (
+
+
+
+ ),
+ education: (
+
+
+
+
+ ),
+ health: (
+
+
+
+ ),
+ others: (
+
+
+
+ )
+ };
+ return icons[category] || icons.others;
+ };
+
+ return (
+
+
+
+
+ {/* Tabs */}
+
+ {tabs.map((tab) => (
+ setActiveTab(tab.key)}
+ >
+ {tab.label}
+
+ ))}
+
+
+
+ {filteredProjects.map((project, index) => (
+
+
+
+ {getCategoryIcon(project.category)}
+
+
+
+
+
+ {t(`portfolio.projects.${project.key}.title`)}
+
+
+
+ {t(`portfolio.projects.${project.key}.description`)}
+
+
+
+
+
{t('portfolio.techStack')}:
+
+ {project.tech.map((tech) => (
+
+ {tech}
+
+ ))}
+
+
+
+
+ {t(`portfolio.projects.${project.key}.role`)}
+
+
+
+
+
+
+
{t(`portfolio.projects.${project.key}.impact`)}
+
+
+
+
+ {t('portfolio.viewProject')}
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default Portfolio;
diff --git a/src/components/sections/Portfolio.module.css b/src/components/sections/Portfolio.module.css
new file mode 100644
index 000000000..df998481d
--- /dev/null
+++ b/src/components/sections/Portfolio.module.css
@@ -0,0 +1,213 @@
+.portfolio {
+ background: var(--bg);
+}
+
+.tabs {
+ display: flex;
+ gap: var(--space-2);
+ margin-bottom: var(--space-8);
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.tab {
+ padding: var(--space-2) var(--space-4);
+ background: var(--bg-2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-full);
+ font-size: var(--text-sm);
+ font-weight: var(--font-medium);
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: all var(--transition-base);
+}
+
+.tab:hover {
+ background: var(--bg);
+ border-color: var(--primary);
+ color: var(--primary);
+}
+
+.tabActive {
+ background: var(--primary);
+ border-color: var(--primary);
+ color: white;
+}
+
+.tabActive:hover {
+ background: var(--primary-2);
+ border-color: var(--primary-2);
+}
+
+.projectsGrid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
+ gap: var(--space-6);
+}
+
+.projectCard {
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+ overflow: hidden;
+ animation: fadeIn 0.6s ease-out backwards;
+}
+
+.projectHeader {
+ height: 180px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+}
+
+.projectHeader::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.2);
+ transition: opacity var(--transition-base);
+}
+
+.projectCard:hover .projectHeader::before {
+ opacity: 0;
+}
+
+.projectIcon {
+ position: relative;
+ z-index: 1;
+ color: white;
+ transition: transform var(--transition-base);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2));
+}
+
+.projectIcon svg {
+ width: 64px;
+ height: 64px;
+}
+
+.projectCard:hover .projectIcon {
+ transform: scale(1.15) rotate(5deg);
+}
+
+.projectContent {
+ padding: var(--space-4);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+ flex: 1;
+}
+
+.projectTitle {
+ font-size: var(--text-xl);
+ font-weight: var(--font-bold);
+ color: var(--text);
+}
+
+.projectDescription {
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+ line-height: 1.6;
+}
+
+.projectMeta {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-2);
+ margin-top: auto;
+ padding-top: var(--space-3);
+ border-top: 1px solid var(--border);
+}
+
+.metaItem {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-1);
+}
+
+.metaLabel {
+ font-size: var(--text-xs);
+ font-weight: var(--font-semibold);
+ color: var(--text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.techStack {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--space-1);
+}
+
+.techBadge {
+ font-family: var(--font-mono);
+ font-size: var(--text-xs);
+ padding: 0.25rem 0.5rem;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ color: var(--primary);
+}
+
+.projectRole {
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+}
+
+.projectImpact {
+ display: flex;
+ align-items: center;
+ gap: var(--space-1);
+ font-size: var(--text-sm);
+ color: var(--accent);
+ font-weight: var(--font-medium);
+}
+
+.projectImpact svg {
+ flex-shrink: 0;
+}
+
+.projectLink {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-2);
+ padding: var(--space-2) var(--space-4);
+ margin-top: var(--space-3);
+ background: var(--primary);
+ color: white;
+ text-decoration: none;
+ border-radius: var(--radius-md);
+ font-size: var(--text-sm);
+ font-weight: var(--font-semibold);
+ transition: all var(--transition-base);
+}
+
+.projectLink:hover {
+ background: var(--primary-2);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3);
+}
+
+.projectLink svg {
+ width: 16px;
+ height: 16px;
+ transition: transform var(--transition-base);
+}
+
+.projectLink:hover svg {
+ transform: translateX(2px);
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .projectsGrid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/components/sections/Services.jsx b/src/components/sections/Services.jsx
new file mode 100644
index 000000000..81448489a
--- /dev/null
+++ b/src/components/sections/Services.jsx
@@ -0,0 +1,102 @@
+import { useLanguage } from '../../contexts/LanguageContext';
+import SectionTitle from '../common/SectionTitle';
+import Card from '../common/Card';
+import styles from './Services.module.css';
+
+/**
+ * Services Section - Main service offerings
+ */
+const Services = () => {
+ const { t } = useLanguage();
+
+ const services = [
+ {
+ key: 'development',
+ icon: (
+
+
+
+
+ ),
+ color: 'var(--primary)',
+ },
+ {
+ key: 'architecture',
+ icon: (
+
+
+
+
+ ),
+ color: 'var(--primary-2)',
+ },
+ {
+ key: 'consulting',
+ icon: (
+
+
+
+
+
+
+ ),
+ color: 'var(--accent)',
+ },
+ ];
+
+ // Get features for a service
+ const getFeatures = (serviceKey) => {
+ const serviceData = t(`services.${serviceKey}`);
+ return serviceData?.features || [];
+ };
+
+ return (
+
+
+
+
+
+ {services.map((service, index) => (
+
+
+ {service.icon}
+
+
+
+ {t(`services.${service.key}.title`)}
+
+
+
+ {t(`services.${service.key}.description`)}
+
+
+
+ {getFeatures(service.key).map((feature, idx) => (
+
+
+
+
+ {feature}
+
+ ))}
+
+
+ ))}
+
+
+
+ );
+};
+
+export default Services;
diff --git a/src/components/sections/Services.module.css b/src/components/sections/Services.module.css
new file mode 100644
index 000000000..1c5aeba72
--- /dev/null
+++ b/src/components/sections/Services.module.css
@@ -0,0 +1,74 @@
+.services {
+ background: var(--bg-2);
+}
+
+.servicesGrid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
+ gap: var(--space-6);
+}
+
+.serviceCard {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+ animation: fadeIn 0.6s ease-out backwards;
+}
+
+.serviceIcon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 64px;
+ height: 64px;
+ border-radius: var(--radius-xl);
+ color: white;
+ margin-bottom: var(--space-2);
+ transition: transform var(--transition-base);
+}
+
+.serviceCard:hover .serviceIcon {
+ transform: scale(1.1) rotate(5deg);
+}
+
+.serviceTitle {
+ font-size: var(--text-2xl);
+ font-weight: var(--font-bold);
+ color: var(--text);
+ margin-bottom: var(--space-2);
+}
+
+.serviceDescription {
+ font-size: var(--text-base);
+ color: var(--text-secondary);
+ line-height: 1.7;
+ margin-bottom: var(--space-3);
+}
+
+.featureList {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-2);
+ margin-top: auto;
+}
+
+.featureItem {
+ display: flex;
+ align-items: flex-start;
+ gap: var(--space-2);
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+}
+
+.featureItem svg {
+ flex-shrink: 0;
+ margin-top: 2px;
+ color: var(--primary);
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .servicesGrid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/components/sections/TechStack.jsx b/src/components/sections/TechStack.jsx
new file mode 100644
index 000000000..efaa6f2fc
--- /dev/null
+++ b/src/components/sections/TechStack.jsx
@@ -0,0 +1,101 @@
+import { useLanguage } from '../../contexts/LanguageContext';
+import SectionTitle from '../common/SectionTitle';
+import Card from '../common/Card';
+import styles from './TechStack.module.css';
+
+/**
+ * TechStack Section - Technologies and tools showcase
+ */
+const TechStack = () => {
+ const { t } = useLanguage();
+
+ const techCategories = [
+ {
+ key: 'backend',
+ icon: (
+
+
+
+
+ ),
+ color: 'var(--primary)',
+ technologies: ['Java', 'Kotlin', 'Spring Boot', 'Node.js', 'Python']
+ },
+ {
+ key: 'cloud',
+ icon: (
+
+
+
+ ),
+ color: 'var(--primary-2)',
+ technologies: ['AWS', 'Docker', 'Kubernetes', 'Jenkins', 'GitLab', 'Azure DevOps']
+ },
+ {
+ key: 'databases',
+ icon: (
+
+
+
+
+
+ ),
+ color: 'var(--accent)',
+ technologies: ['PostgreSQL', 'MySQL', 'DynamoDB', 'Redis', 'Kafka']
+ },
+ {
+ key: 'observability',
+ icon: (
+
+
+
+
+ ),
+ color: 'var(--primary)',
+ technologies: ['Grafana', 'Splunk', 'New Relic', 'Kibana', 'SonarQube']
+ }
+ ];
+
+ return (
+
+
+
+
+
+ {techCategories.map((category, index) => (
+
+
+
+ {category.icon}
+
+
+ {t(`techStack.categories.${category.key}.title`)}
+
+
+
+
+ {category.technologies.map((tech) => (
+
+ {tech}
+
+ ))}
+
+
+ ))}
+
+
+
+ );
+};
+
+export default TechStack;
diff --git a/src/components/sections/TechStack.module.css b/src/components/sections/TechStack.module.css
new file mode 100644
index 000000000..b6161d6db
--- /dev/null
+++ b/src/components/sections/TechStack.module.css
@@ -0,0 +1,102 @@
+.techStack {
+ background: var(--bg);
+ padding: var(--space-20) 0;
+}
+
+.categoriesGrid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: var(--space-6);
+}
+
+.categoryCard {
+ padding: var(--space-5);
+ animation: fadeIn 0.6s ease-out backwards;
+ transition: transform var(--transition-base);
+}
+
+.categoryCard:hover {
+ transform: translateY(-4px);
+}
+
+.categoryHeader {
+ display: flex;
+ align-items: center;
+ gap: var(--space-3);
+ margin-bottom: var(--space-4);
+}
+
+.categoryIcon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 48px;
+ height: 48px;
+ border-radius: var(--radius-lg);
+ background: var(--bg-2);
+ border: 1px solid var(--border);
+ flex-shrink: 0;
+}
+
+.categoryTitle {
+ font-size: var(--text-xl);
+ font-weight: var(--font-bold);
+ color: var(--text);
+}
+
+.technologiesGrid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--space-2);
+}
+
+.techBadge {
+ display: inline-flex;
+ align-items: center;
+ padding: var(--space-2) var(--space-3);
+ background: var(--bg-2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ font-family: var(--font-mono);
+ font-size: var(--text-sm);
+ font-weight: var(--font-medium);
+ color: var(--text);
+ transition: all var(--transition-base);
+ cursor: default;
+}
+
+.techBadge:hover {
+ background: var(--primary);
+ color: white;
+ border-color: var(--primary);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3);
+}
+
+/* Animations */
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .categoriesGrid {
+ grid-template-columns: 1fr;
+ }
+
+ .categoryHeader {
+ flex-direction: column;
+ text-align: center;
+ }
+
+ .technologiesGrid {
+ justify-content: center;
+ }
+}
diff --git a/src/contexts/LanguageContext.jsx b/src/contexts/LanguageContext.jsx
new file mode 100644
index 000000000..45c736894
--- /dev/null
+++ b/src/contexts/LanguageContext.jsx
@@ -0,0 +1,76 @@
+import { createContext, useContext, useState, useEffect } from 'react';
+
+const LanguageContext = createContext();
+
+/**
+ * LanguageProvider - Manages i18n state and translations
+ * Dynamically imports translation files based on selected language
+ */
+export const LanguageProvider = ({ children }) => {
+ const [language, setLanguage] = useState('es');
+ const [translations, setTranslations] = useState({});
+ const [isLoading, setIsLoading] = useState(true);
+
+ // Load translations when language changes
+ useEffect(() => {
+ setIsLoading(true);
+ import(`../locales/${language}.json`)
+ .then((module) => {
+ setTranslations(module.default);
+ setIsLoading(false);
+ })
+ .catch((error) => {
+ console.error(`Error loading translations for ${language}:`, error);
+ setIsLoading(false);
+ });
+ }, [language]);
+
+ /**
+ * Translation function - navigates nested translation objects
+ * @param {string} key - Dot-notation key (e.g., 'hero.title')
+ * @returns {string} - Translated text or key if not found
+ */
+ const t = (key) => {
+ const keys = key.split('.');
+ let result = translations;
+
+ for (const k of keys) {
+ if (result && typeof result === 'object') {
+ result = result[k];
+ } else {
+ return key; // Return key if translation not found
+ }
+ }
+
+ return result || key;
+ };
+
+ const toggleLanguage = () => {
+ setLanguage((prevLang) => (prevLang === 'es' ? 'en' : 'es'));
+ };
+
+ const value = {
+ language,
+ setLanguage,
+ toggleLanguage,
+ t,
+ isLoading,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Custom hook to use language context
+ */
+export const useLanguage = () => {
+ const context = useContext(LanguageContext);
+ if (!context) {
+ throw new Error('useLanguage must be used within a LanguageProvider');
+ }
+ return context;
+};
diff --git a/src/contexts/ThemeContext.jsx b/src/contexts/ThemeContext.jsx
new file mode 100644
index 000000000..783c4fc8e
--- /dev/null
+++ b/src/contexts/ThemeContext.jsx
@@ -0,0 +1,43 @@
+import { createContext, useContext, useState, useEffect } from 'react';
+
+const ThemeContext = createContext();
+
+/**
+ * ThemeProvider - Manages dark/light theme state
+ * Uses React state instead of localStorage (not supported in this environment)
+ */
+export const ThemeProvider = ({ children }) => {
+ const [theme, setTheme] = useState('dark');
+
+ // Apply theme to document root
+ useEffect(() => {
+ document.documentElement.setAttribute('data-theme', theme);
+ }, [theme]);
+
+ const toggleTheme = () => {
+ setTheme((prevTheme) => (prevTheme === 'dark' ? 'light' : 'dark'));
+ };
+
+ const value = {
+ theme,
+ toggleTheme,
+ isDark: theme === 'dark',
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Custom hook to use theme context
+ */
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+};
diff --git a/src/hooks/useIntersectionObserver.js b/src/hooks/useIntersectionObserver.js
new file mode 100644
index 000000000..23534eb61
--- /dev/null
+++ b/src/hooks/useIntersectionObserver.js
@@ -0,0 +1,37 @@
+import { useEffect, useRef, useState } from 'react';
+
+/**
+ * Custom hook for Intersection Observer API
+ * Useful for scroll animations and lazy loading
+ *
+ * @param {Object} options - Intersection Observer options
+ * @returns {Array} [ref, isIntersecting] - Ref to attach and intersection state
+ */
+export const useIntersectionObserver = (options = {}) => {
+ const ref = useRef(null);
+ const [isIntersecting, setIsIntersecting] = useState(false);
+
+ useEffect(() => {
+ const element = ref.current;
+ if (!element) return;
+
+ const observer = new IntersectionObserver(([entry]) => {
+ setIsIntersecting(entry.isIntersecting);
+ }, {
+ threshold: 0.1,
+ ...options,
+ });
+
+ observer.observe(element);
+
+ return () => {
+ if (element) {
+ observer.unobserve(element);
+ }
+ };
+ }, [options]);
+
+ return [ref, isIntersecting];
+};
+
+export default useIntersectionObserver;
diff --git a/src/locales/en.json b/src/locales/en.json
new file mode 100644
index 000000000..517858e7e
--- /dev/null
+++ b/src/locales/en.json
@@ -0,0 +1,272 @@
+{
+ "nav": {
+ "about": "About",
+ "services": "Services",
+ "portfolio": "Portfolio",
+ "contact": "Contact"
+ },
+ "hero": {
+ "greeting": "Hi, I'm Luis Fernando Benavides Rengifo",
+ "title": "Senior Software Architect",
+ "titleHighlight": "22+ years | Fintech & E-commerce | Mercado Libre",
+ "subtitle": "Developing high-impact solutions for leading companies like Mercado Libre, Rappi, Nequi, and Protección. Specialized in microservices architecture, fintech, and digital transformation with proven results: 90% error reduction, 90% performance improvement in transactions.",
+ "cta": "View my work",
+ "ctaSecondary": "Get in touch"
+ },
+ "about": {
+ "title": "About Me",
+ "subtitle": "Experience and professional background",
+ "intro": "Master in Software Engineering (PUJ Cali, distinguished scholarship recipient from Valle del Cauca Government) with 22+ years developing high-impact solutions for leading Latin American tech companies: Mercado Libre, Rappi, Nequi, and Protección. PMI, Scrum Master, CAPM, and ITIL V3 certified.\n\nSpecialized in microservices architecture, fintech, e-commerce, and digital transformation. Self-taught professional committed to technical excellence and continuous improvement.",
+ "education": {
+ "title": "Education",
+ "degree": "Master in Software Engineering",
+ "institution": "Pontificia Universidad Javeriana Cali"
+ },
+ "experience": {
+ "title": "Experience",
+ "years": "22+",
+ "yearsLabel": "years in the industry",
+ "projects": "100+",
+ "projectsLabel": "completed projects",
+ "clients": "50+",
+ "clientsLabel": "satisfied clients",
+ "sectorsLabel": "sectors served"
+ },
+ "expertise": {
+ "title": "Areas of Expertise",
+ "item1": "Microservices Architecture - Mercado Libre, Rappi, Nequi (90% error reduction)",
+ "item2": "Cloud & DevOps (AWS) - Lambda, EKS, Docker, Kubernetes, CI/CD",
+ "item3": "Backend Development - Java/Kotlin, Spring Boot, Kafka, Redis, PostgreSQL",
+ "item4": "Technical Leadership - Fintech & e-commerce teams, Scrum Master certified",
+ "item5": "Digital Transformation - 90% performance improvement, 80% cost reduction",
+ "item6": "Observability & Monitoring - Grafana, Splunk, New Relic, Kibana"
+ }
+ },
+ "techStack": {
+ "title": "Tech Stack",
+ "subtitle": "Technologies and tools I work with",
+ "categories": {
+ "backend": {
+ "title": "Backend & Languages"
+ },
+ "cloud": {
+ "title": "Cloud & DevOps"
+ },
+ "databases": {
+ "title": "Databases & Messaging"
+ },
+ "observability": {
+ "title": "Observability & Monitoring"
+ }
+ }
+ },
+ "services": {
+ "title": "Services",
+ "subtitle": "Specialized solutions based on proven experience",
+ "development": {
+ "title": "Backend Development & Microservices Architecture",
+ "description": "Specialized backend development with Java/Kotlin and Spring Boot. Design and implementation of microservices architectures for fintech, e-commerce, and digital banking. Proven experience at Mercado Libre, Rappi, and Nequi.",
+ "features": [
+ "Backend with Java/Kotlin and Spring Boot",
+ "Scalable microservices architecture",
+ "REST APIs, Kafka, Redis, PostgreSQL",
+ "Patterns: Circuit Breaker, Saga, Event Sourcing",
+ "Solutions for fintech and e-commerce"
+ ]
+ },
+ "architecture": {
+ "title": "Cloud Architecture & DevOps",
+ "description": "Cloud architecture on AWS and DevOps practices implementation. Migration from on-premise to cloud, containerization with Docker/Kubernetes, and CI/CD. Achievements: 90% performance improvement, deployment time reduced from 20 to 2 minutes.",
+ "features": [
+ "AWS: Lambda, EKS, S3, CloudWatch, SNS",
+ "Docker, Kubernetes, containerization",
+ "CI/CD: Jenkins, GitLab, Azure DevOps",
+ "Observability: Grafana, Splunk, New Relic",
+ "On-premise to cloud migration"
+ ]
+ },
+ "consulting": {
+ "title": "Technical Leadership & Digital Transformation",
+ "description": "Technical team leadership in critical projects and digital transformation. Agile methodologies implementation, process optimization, and technical mentoring. Results: 80% cost reduction, 50% improvement in learning curve.",
+ "features": [
+ "Technical team leadership",
+ "Scrum and agile methodologies implementation",
+ "Legacy systems modernization",
+ "Operational cost reduction",
+ "Technical mentoring and training"
+ ]
+ }
+ },
+ "portfolio": {
+ "title": "Portfolio",
+ "subtitle": "Featured projects",
+ "viewProject": "View project",
+ "techStack": "Technologies",
+ "tabs": {
+ "all": "Featured",
+ "fintech": "Fintech & E-commerce",
+ "cloud": "Cloud & DevOps",
+ "education": "Education",
+ "health": "Healthcare",
+ "others": "Others"
+ },
+ "projects": {
+ "lowcode": {
+ "title": "Feasibility Study for Low-Code Platform Implementation",
+ "description": "Graduate project for Master's degree in Software Engineering. Feasibility study for low-code platform at Pontificia Universidad Javeriana, Cali.",
+ "role": "Researcher and Architect",
+ "impact": "Master's Thesis - PUJ Cali",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2020/01/estudio-de-factibilidad-para-la.html"
+ },
+ "freedcamp": {
+ "title": "Web Project with Spring Boot - Freedcamp REST API",
+ "description": "Technical test to validate programming skills. Integration with Freedcamp REST API.",
+ "role": "Backend Developer",
+ "impact": "External API Integration",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2020/01/implementacion-proyecto-web-con.html"
+ },
+ "apirest": {
+ "title": "REST API Project Implementation with Spring Boot",
+ "description": "Technical test to validate programming skills. REST API development with Spring Boot.",
+ "role": "Full Stack Developer",
+ "impact": "Modern REST Architecture",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/12/implementacion-proyecto-api-rest-con.html"
+ },
+ "bootstrap": {
+ "title": "Web Project with Spring Boot and Bootstrap",
+ "description": "Technical test for Full Stack Developer. Web development with Spring Boot and Bootstrap.",
+ "role": "Full Stack Developer",
+ "impact": "Frontend and Backend Development",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/11/implementacion-proyecto-web-con.html"
+ },
+ "lambda": {
+ "title": "REST API Services using AWS Lambda",
+ "description": "Final project for Advanced Software Engineering Topics. Three REST API services with AWS Lambda.",
+ "role": "Cloud Architect",
+ "impact": "AWS Serverless Architecture",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/11/implementacion-de-servicios-api-rest.html"
+ },
+ "sms": {
+ "title": "SMS Messaging Implementation",
+ "description": "Allows sending SMS messages to affiliates and employers of a health promoting entity.",
+ "role": "Backend Developer",
+ "impact": "Health Sector - EPS",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/08/implementacion-de-envio-de-mensajes-sms.html"
+ },
+ "ci": {
+ "title": "Continuous Integration Implementation - WebStore Project",
+ "description": "Final project for Software Construction and Testing. Continuous integration, automated testing and static analysis.",
+ "role": "DevOps Engineer",
+ "impact": "CI/CD and Testing",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/implementacion-de-integracion-continua.html"
+ },
+ "patrones": {
+ "title": "Design Patterns Implementation",
+ "description": "Final project for Software Design Strategies and Patterns. Implementation of design patterns.",
+ "role": "Software Architect",
+ "impact": "Design Patterns",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/implementacion-de-patrones-de-diseno-un.html"
+ },
+ "bolsa": {
+ "title": "Job Board Integration with PeopleSoft",
+ "description": "Allows creating institutional users to the external employment platform.",
+ "role": "Integration Developer",
+ "impact": "Education Sector",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/proyecto-que-integra-la-bolsa-de-empleo.html"
+ },
+ "salesforce": {
+ "title": "Salesforce CRM Integration with PeopleSoft",
+ "description": "Allows tracking admission candidates from opportunity creation to payment and enrollment.",
+ "role": "Integration Developer",
+ "impact": "Education Sector - CRM",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/proyecto-que-integra-crm-sales-force.html"
+ },
+ "mega": {
+ "title": "Authorization and Care Management Improvement (MEGA)",
+ "description": "Ensures health benefits management (POS, NO POS, PAC and Subsidized POS), optimizing care process.",
+ "role": "Software Architect",
+ "impact": "Health Sector - EPS",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/mejoramiento-en-la-gestion-de-las.html"
+ },
+ "fosyga": {
+ "title": "Software for Law Resolution 458 of 2013 Compliance (Fosyga)",
+ "description": "Complies with Law Resolution 458 of 2013, which unifies the reimbursement procedure before the Solidarity and Guarantee Fund.",
+ "role": "Backend Developer",
+ "impact": "Health Sector - Legal Compliance",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/desarrollo-e-implementacion-de-software.html"
+ },
+ "arco": {
+ "title": "Production Order Control Software (ARCO)",
+ "description": "Allows tracking orders during the production process.",
+ "role": "Full Stack Developer",
+ "impact": "Industrial Sector",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-desarrollo-e-implementacion-de.html"
+ },
+ "tarjeta": {
+ "title": "Credit Card Control and Administration Software",
+ "description": "Allows keeping a record of company credit card customers from application to delivery.",
+ "role": "Backend Developer",
+ "impact": "Commercial Sector",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-desarrollo-y-implementacion-de.html"
+ },
+ "softcontrol": {
+ "title": "SOFTCONTROL Application - Disciplinary Process Management",
+ "description": "Creation of query, statistics, control and tracking, reports, management user and complaint reception modules.",
+ "role": "Full Stack Developer",
+ "impact": "Public Sector",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-y-desarrollo-de-un-aplicativo.html"
+ },
+ "inventarios": {
+ "title": "Inventory Control Software",
+ "description": "Allows capturing data from the billing process and generating various reports from this information.",
+ "role": "Backend Developer",
+ "impact": "Commercial Sector",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-y-desarrollo-de-software-para-el.html"
+ }
+ }
+ },
+ "contact": {
+ "title": "Contact",
+ "subtitle": "Let's talk about your project",
+ "description": "Have a project in mind or need technical consulting? I'm available for new opportunities and collaborations.",
+ "form": {
+ "name": "Full name",
+ "namePlaceholder": "Your name",
+ "email": "Email address",
+ "emailPlaceholder": "your@email.com",
+ "subject": "Subject",
+ "subjectPlaceholder": "How can I help you?",
+ "message": "Message",
+ "messagePlaceholder": "Tell me about your project...",
+ "submit": "Send message",
+ "sending": "Sending...",
+ "success": "Message sent! I'll respond soon.",
+ "error": "Error sending. Please try again."
+ },
+ "info": {
+ "email": "lfbenavides@gmail.com",
+ "location": "Available remotely",
+ "availability": "Available for projects"
+ },
+ "social": {
+ "linkedin": "LinkedIn",
+ "github": "GitHub",
+ "twitter": "Twitter"
+ }
+ },
+ "footer": {
+ "rights": "All rights reserved",
+ "madeWith": "Made with",
+ "by": "by"
+ },
+ "theme": {
+ "toggle": "Toggle theme",
+ "dark": "Dark mode",
+ "light": "Light mode"
+ },
+ "language": {
+ "toggle": "Change language",
+ "es": "Español",
+ "en": "English"
+ }
+}
diff --git a/src/locales/es.json b/src/locales/es.json
new file mode 100644
index 000000000..4e8871f52
--- /dev/null
+++ b/src/locales/es.json
@@ -0,0 +1,272 @@
+{
+ "nav": {
+ "about": "Sobre mí",
+ "services": "Servicios",
+ "portfolio": "Portafolio",
+ "contact": "Contacto"
+ },
+ "hero": {
+ "greeting": "Hola, soy Luis Fernando Benavides Rengifo",
+ "title": "Arquitecto de Software Senior",
+ "titleHighlight": "22+ años | Fintech & E-commerce | Mercado Libre",
+ "subtitle": "Desarrollando soluciones de alto impacto para empresas líderes como Mercado Libre, Rappi, Nequi y Protección. Especializado en arquitectura de microservicios, fintech y transformación digital con resultados comprobados: 90% reducción de errores, 90% mejora en rendimiento de transacciones.",
+ "cta": "Ver mi trabajo",
+ "ctaSecondary": "Contactar"
+ },
+ "about": {
+ "title": "Sobre mí",
+ "subtitle": "Experiencia y trayectoria profesional",
+ "intro": "Magíster en Ingeniería de Software (PUJ Cali, becario destacado Gobernación del Valle) con 22+ años desarrollando soluciones de alto impacto para empresas tecnológicas líderes de Latinoamérica: Mercado Libre, Rappi, Nequi y Protección. Certificado PMI, Scrum Master, CAPM e ITIL V3.\n\nEspecializado en arquitectura de microservicios, fintech, e-commerce y transformación digital. Autodidacta comprometido con la excelencia técnica y la mejora continua.",
+ "education": {
+ "title": "Educación",
+ "degree": "Magíster en Ingeniería de Software",
+ "institution": "Pontificia Universidad Javeriana Cali"
+ },
+ "experience": {
+ "title": "Experiencia",
+ "years": "22+",
+ "yearsLabel": "años en la industria",
+ "projects": "100+",
+ "projectsLabel": "proyectos completados",
+ "clients": "50+",
+ "clientsLabel": "clientes satisfechos",
+ "sectorsLabel": "sectores atendidos"
+ },
+ "expertise": {
+ "title": "Áreas de Expertise",
+ "item1": "Microservices Architecture - Mercado Libre, Rappi, Nequi (90% reducción de errores)",
+ "item2": "Cloud & DevOps (AWS) - Lambda, EKS, Docker, Kubernetes, CI/CD",
+ "item3": "Backend Development - Java/Kotlin, Spring Boot, Kafka, Redis, PostgreSQL",
+ "item4": "Technical Leadership - Equipos fintech & e-commerce, Scrum Master certified",
+ "item5": "Digital Transformation - 90% mejora en rendimiento, 80% reducción de costos",
+ "item6": "Observability & Monitoring - Grafana, Splunk, New Relic, Kibana"
+ }
+ },
+ "techStack": {
+ "title": "Stack Tecnológico",
+ "subtitle": "Tecnologías y herramientas con las que trabajo",
+ "categories": {
+ "backend": {
+ "title": "Backend & Languages"
+ },
+ "cloud": {
+ "title": "Cloud & DevOps"
+ },
+ "databases": {
+ "title": "Databases & Messaging"
+ },
+ "observability": {
+ "title": "Observability & Monitoring"
+ }
+ }
+ },
+ "services": {
+ "title": "Servicios",
+ "subtitle": "Soluciones especializadas basadas en experiencia comprobada",
+ "development": {
+ "title": "Desarrollo Backend & Arquitectura de Microservicios",
+ "description": "Desarrollo backend especializado con Java/Kotlin y Spring Boot. Diseño e implementación de arquitecturas de microservicios para fintech, e-commerce y banca digital. Experiencia comprobada en Mercado Libre, Rappi y Nequi.",
+ "features": [
+ "Backend con Java/Kotlin y Spring Boot",
+ "Arquitectura de microservicios escalables",
+ "APIs REST, Kafka, Redis, PostgreSQL",
+ "Patrones: Circuit Breaker, Saga, Event Sourcing",
+ "Soluciones para fintech y e-commerce"
+ ]
+ },
+ "architecture": {
+ "title": "Cloud Architecture & DevOps",
+ "description": "Arquitectura cloud en AWS y implementación de prácticas DevOps. Migración de sistemas on-premise a la nube, containerización con Docker/Kubernetes y CI/CD. Logros: 90% mejora en rendimiento, reducción de despliegues de 20 a 2 minutos.",
+ "features": [
+ "AWS: Lambda, EKS, S3, CloudWatch, SNS",
+ "Docker, Kubernetes, containerización",
+ "CI/CD: Jenkins, GitLab, Azure DevOps",
+ "Observabilidad: Grafana, Splunk, New Relic",
+ "Migración on-premise a cloud"
+ ]
+ },
+ "consulting": {
+ "title": "Liderazgo Técnico & Transformación Digital",
+ "description": "Liderazgo de equipos técnicos en proyectos críticos y transformación digital. Implementación de metodologías ágiles, optimización de procesos y mentoría técnica. Resultados: 80% reducción de costos, 50% mejora en curva de aprendizaje.",
+ "features": [
+ "Liderazgo de equipos técnicos",
+ "Implementación de Scrum y metodologías ágiles",
+ "Modernización de sistemas legacy",
+ "Reducción de costos operativos",
+ "Mentoría y capacitación técnica"
+ ]
+ }
+ },
+ "portfolio": {
+ "title": "Portafolio",
+ "subtitle": "Proyectos destacados",
+ "viewProject": "Ver proyecto",
+ "techStack": "Tecnologías",
+ "tabs": {
+ "all": "Destacados",
+ "fintech": "Fintech & E-commerce",
+ "cloud": "Cloud & DevOps",
+ "education": "Educación",
+ "health": "Salud",
+ "others": "Otros"
+ },
+ "projects": {
+ "lowcode": {
+ "title": "Estudio de factibilidad para implementación de plataforma low-code",
+ "description": "Proyecto de grado para optar al título de Magíster en Ingeniería de Software. Estudio de factibilidad para la implementación de una plataforma low-code en la Pontificia Universidad Javeriana, seccional Cali.",
+ "role": "Investigador y Arquitecto",
+ "impact": "Proyecto de Maestría - PUJ Cali",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2020/01/estudio-de-factibilidad-para-la.html"
+ },
+ "freedcamp": {
+ "title": "Implementación proyecto Web con Spring Boot - API REST Freedcamp",
+ "description": "Prueba técnica para validar habilidades de programación. Invocación de API REST de Freedcamp.",
+ "role": "Desarrollador Backend",
+ "impact": "Integración con API externa",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2020/01/implementacion-proyecto-web-con.html"
+ },
+ "apirest": {
+ "title": "Implementación proyecto API REST con Spring Boot",
+ "description": "Prueba técnica para validar habilidades de programación. Desarrollo de API REST con Spring Boot.",
+ "role": "Desarrollador Full Stack",
+ "impact": "Arquitectura REST moderna",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/12/implementacion-proyecto-api-rest-con.html"
+ },
+ "bootstrap": {
+ "title": "Implementación proyecto Web con Spring Boot y Bootstrap",
+ "description": "Prueba técnica para Full Stack Developer. Desarrollo web con Spring Boot y Bootstrap.",
+ "role": "Desarrollador Full Stack",
+ "impact": "Desarrollo Frontend y Backend",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/11/implementacion-proyecto-web-con.html"
+ },
+ "lambda": {
+ "title": "Implementación de servicios API REST mediante AWS Lambda",
+ "description": "Proyecto final de Tópicos Avanzados de Ingeniería de Software. Tres servicios API REST con AWS Lambda.",
+ "role": "Arquitecto Cloud",
+ "impact": "Arquitectura Serverless AWS",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/11/implementacion-de-servicios-api-rest.html"
+ },
+ "sms": {
+ "title": "Implementación de envío de mensajes SMS",
+ "description": "Permite el envío de mensajes SMS a afiliados y empleadores de una entidad promotora de salud.",
+ "role": "Desarrollador Backend",
+ "impact": "Sector Salud - EPS",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/08/implementacion-de-envio-de-mensajes-sms.html"
+ },
+ "ci": {
+ "title": "Implementación de Integración continua proyecto WebStore",
+ "description": "Proyecto final de Construcción y Pruebas de Software. Integración continua, pruebas automatizadas y análisis estático.",
+ "role": "DevOps Engineer",
+ "impact": "CI/CD y Testing",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/implementacion-de-integracion-continua.html"
+ },
+ "patrones": {
+ "title": "Implementación de patrones de diseño a un proyecto de pregrado",
+ "description": "Proyecto final de Estrategias y Patrones de Diseño de Software. Implementación de patrones de diseño.",
+ "role": "Arquitecto de Software",
+ "impact": "Patrones de Diseño",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/implementacion-de-patrones-de-diseno-un.html"
+ },
+ "bolsa": {
+ "title": "Integración de la bolsa de empleo de egresados con PeopleSoft",
+ "description": "Permite crear usuarios institucionales a la plataforma externa del empleo.",
+ "role": "Desarrollador de Integraciones",
+ "impact": "Sector Educación",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/proyecto-que-integra-la-bolsa-de-empleo.html"
+ },
+ "salesforce": {
+ "title": "Integración CRM Sales Force con PeopleSoft",
+ "description": "Permite realizar seguimiento de los candidatos a admisión desde que se crea la oportunidad hasta que paga y se matricula.",
+ "role": "Desarrollador de Integraciones",
+ "impact": "Sector Educación - CRM",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/proyecto-que-integra-crm-sales-force.html"
+ },
+ "mega": {
+ "title": "Mejoramiento en la Gestión de las Autorizaciones y la Atención (MEGA)",
+ "description": "Garantiza la gestión de las prestaciones en salud (POS, NO POS, PAC Y POS Subsidiado), optimizando el proceso de atención.",
+ "role": "Arquitecto de Software",
+ "impact": "Sector Salud - EPS",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/mejoramiento-en-la-gestion-de-las.html"
+ },
+ "fosyga": {
+ "title": "Software para cumplir con Resolución de Ley 458 del 2013 (Fosyga)",
+ "description": "Cumple con Resolución de Ley 458 del 2013, por la cual se unifica el procedimiento de recobro ante el Fondo de Solidaridad y Garantía.",
+ "role": "Desarrollador Backend",
+ "impact": "Sector Salud - Cumplimiento Legal",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/desarrollo-e-implementacion-de-software.html"
+ },
+ "arco": {
+ "title": "Software para control de órdenes de producción (ARCO)",
+ "description": "Permite realizar seguimiento a las órdenes durante el proceso de producción.",
+ "role": "Desarrollador Full Stack",
+ "impact": "Sector Industrial",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-desarrollo-e-implementacion-de.html"
+ },
+ "tarjeta": {
+ "title": "Software para control y administración de tarjetas de crédito",
+ "description": "Permite llevar un registro de clientes de la tarjeta propia de la empresa desde su solicitud hasta la entrega.",
+ "role": "Desarrollador Backend",
+ "impact": "Sector Comercial",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-desarrollo-y-implementacion-de.html"
+ },
+ "softcontrol": {
+ "title": "Aplicativo SOFTCONTROL - Gestión de procesos disciplinarios",
+ "description": "Creación de módulos de consulta, estadísticas, control y seguimiento, reportes, usuario gerencial y recepción de quejas.",
+ "role": "Desarrollador Full Stack",
+ "impact": "Sector Público",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-y-desarrollo-de-un-aplicativo.html"
+ },
+ "inventarios": {
+ "title": "Software para el control de inventarios",
+ "description": "Permite realizar una captura de datos del proceso de facturación y a partir de esta información la generación de variados informes.",
+ "role": "Desarrollador Backend",
+ "impact": "Sector Comercial",
+ "link": "https://luisfernandobenavidesrengifo.blogspot.com/2019/01/diseno-y-desarrollo-de-software-para-el.html"
+ }
+ }
+ },
+ "contact": {
+ "title": "Contacto",
+ "subtitle": "Hablemos de tu proyecto",
+ "description": "¿Tienes un proyecto en mente o necesitas consultoría técnica? Estoy disponible para nuevas oportunidades y colaboraciones.",
+ "form": {
+ "name": "Nombre completo",
+ "namePlaceholder": "Tu nombre",
+ "email": "Correo electrónico",
+ "emailPlaceholder": "tu@email.com",
+ "subject": "Asunto",
+ "subjectPlaceholder": "¿En qué puedo ayudarte?",
+ "message": "Mensaje",
+ "messagePlaceholder": "Cuéntame sobre tu proyecto...",
+ "submit": "Enviar mensaje",
+ "sending": "Enviando...",
+ "success": "¡Mensaje enviado! Te responderé pronto.",
+ "error": "Error al enviar. Intenta nuevamente."
+ },
+ "info": {
+ "email": "lfbenavides@gmail.com",
+ "location": "Disponible remotamente",
+ "availability": "Disponible para proyectos"
+ },
+ "social": {
+ "linkedin": "LinkedIn",
+ "github": "GitHub",
+ "twitter": "Twitter"
+ }
+ },
+ "footer": {
+ "rights": "Todos los derechos reservados",
+ "madeWith": "Hecho con",
+ "by": "por"
+ },
+ "theme": {
+ "toggle": "Cambiar tema",
+ "dark": "Modo oscuro",
+ "light": "Modo claro"
+ },
+ "language": {
+ "toggle": "Cambiar idioma",
+ "es": "Español",
+ "en": "English"
+ }
+}
diff --git a/src/main.jsx b/src/main.jsx
new file mode 100644
index 000000000..265cbed70
--- /dev/null
+++ b/src/main.jsx
@@ -0,0 +1,9 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App.jsx';
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+ ,
+);
diff --git a/src/styles/global.css b/src/styles/global.css
new file mode 100644
index 000000000..913883f2e
--- /dev/null
+++ b/src/styles/global.css
@@ -0,0 +1,253 @@
+/* ============================================
+ Global Styles & Reset
+ ============================================ */
+
+@import './variables.css';
+
+/* CSS Reset */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+html {
+ scroll-behavior: smooth;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ font-family: var(--font-primary);
+ font-size: var(--text-base);
+ line-height: 1.6;
+ color: var(--text);
+ background-color: var(--bg);
+ transition: background-color var(--transition-base), color var(--transition-base);
+ overflow-x: hidden;
+}
+
+/* Typography */
+h1, h2, h3, h4, h5, h6 {
+ font-weight: var(--font-bold);
+ line-height: 1.2;
+ color: var(--text);
+ margin-bottom: var(--space-2);
+}
+
+h1 {
+ font-size: var(--text-5xl);
+ font-weight: var(--font-extrabold);
+}
+
+h2 {
+ font-size: var(--text-4xl);
+}
+
+h3 {
+ font-size: var(--text-3xl);
+}
+
+h4 {
+ font-size: var(--text-2xl);
+}
+
+h5 {
+ font-size: var(--text-xl);
+}
+
+h6 {
+ font-size: var(--text-lg);
+}
+
+p {
+ margin-bottom: var(--space-2);
+ color: var(--text-secondary);
+}
+
+a {
+ color: var(--primary);
+ text-decoration: none;
+ transition: color var(--transition-fast);
+}
+
+a:hover {
+ color: var(--primary-2);
+}
+
+/* Lists */
+ul, ol {
+ list-style: none;
+}
+
+/* Images */
+img {
+ max-width: 100%;
+ height: auto;
+ display: block;
+}
+
+/* Buttons */
+button {
+ font-family: inherit;
+ cursor: pointer;
+ border: none;
+ background: none;
+}
+
+/* Inputs */
+input,
+textarea,
+select {
+ font-family: inherit;
+ font-size: inherit;
+ color: inherit;
+}
+
+/* Focus Styles */
+:focus-visible {
+ outline: 2px solid var(--primary);
+ outline-offset: 2px;
+}
+
+/* Selection */
+::selection {
+ background-color: var(--primary);
+ color: white;
+}
+
+/* Scrollbar (Webkit) */
+::-webkit-scrollbar {
+ width: 10px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--bg-2);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--border);
+ border-radius: var(--radius-full);
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--text-secondary);
+}
+
+/* Container */
+.container {
+ width: 100%;
+ max-width: var(--container-max);
+ margin: 0 auto;
+ padding: 0 var(--container-padding);
+}
+
+/* Section Spacing */
+section {
+ padding: var(--space-12) 0;
+}
+
+@media (max-width: 768px) {
+ section {
+ padding: var(--space-8) 0;
+ }
+}
+
+/* Utility Classes */
+.text-gradient {
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-2) 50%, var(--accent) 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.glass {
+ background: var(--glass-bg);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border: 1px solid var(--glass-border);
+}
+
+.shadow {
+ box-shadow: 0 4px 6px -1px var(--shadow), 0 2px 4px -1px var(--shadow);
+}
+
+.shadow-lg {
+ box-shadow: 0 10px 15px -3px var(--shadow-lg), 0 4px 6px -2px var(--shadow);
+}
+
+/* Animations */
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes slideInLeft {
+ from {
+ opacity: 0;
+ transform: translateX(-30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+@keyframes slideInRight {
+ from {
+ opacity: 0;
+ transform: translateX(30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+@keyframes scaleIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+.animate-fade-in {
+ animation: fadeIn var(--transition-slow) ease-out forwards;
+}
+
+.animate-slide-in-left {
+ animation: slideInLeft var(--transition-slow) ease-out forwards;
+}
+
+.animate-slide-in-right {
+ animation: slideInRight var(--transition-slow) ease-out forwards;
+}
+
+.animate-scale-in {
+ animation: scaleIn var(--transition-base) ease-out forwards;
+}
+
+/* Stagger animations for children */
+.stagger-children > * {
+ opacity: 0;
+ animation: fadeIn var(--transition-slow) ease-out forwards;
+}
+
+.stagger-children > *:nth-child(1) { animation-delay: 0ms; }
+.stagger-children > *:nth-child(2) { animation-delay: 100ms; }
+.stagger-children > *:nth-child(3) { animation-delay: 200ms; }
+.stagger-children > *:nth-child(4) { animation-delay: 300ms; }
+.stagger-children > *:nth-child(5) { animation-delay: 400ms; }
+.stagger-children > *:nth-child(6) { animation-delay: 500ms; }
diff --git a/src/styles/variables.css b/src/styles/variables.css
new file mode 100644
index 000000000..ea456f4f9
--- /dev/null
+++ b/src/styles/variables.css
@@ -0,0 +1,113 @@
+/* ============================================
+ CSS Custom Properties - Design System
+ ============================================ */
+
+/* Light Theme */
+:root[data-theme="light"] {
+ /* Colors */
+ --bg: #f7f8fb;
+ --bg-2: #ffffff;
+ --text: #0b1220;
+ --text-secondary: #64748b;
+ --primary: #0ea5e9;
+ --primary-2: #22d3ee;
+ --accent: #a78bfa;
+ --border: #e2e8f0;
+ --shadow: rgba(15, 23, 42, 0.1);
+ --shadow-lg: rgba(15, 23, 42, 0.15);
+
+ /* Glassmorphism */
+ --glass-bg: rgba(255, 255, 255, 0.7);
+ --glass-border: rgba(255, 255, 255, 0.3);
+}
+
+/* Dark Theme */
+:root[data-theme="dark"] {
+ /* Colors */
+ --bg: #0b0f1a;
+ --bg-2: #0f1424;
+ --text: #e7eaf4;
+ --text-secondary: #94a3b8;
+ --primary: #0ea5e9;
+ --primary-2: #22d3ee;
+ --accent: #a78bfa;
+ --border: #1e293b;
+ --shadow: rgba(0, 0, 0, 0.3);
+ --shadow-lg: rgba(0, 0, 0, 0.5);
+
+ /* Glassmorphism */
+ --glass-bg: rgba(15, 20, 36, 0.7);
+ --glass-border: rgba(255, 255, 255, 0.1);
+}
+
+/* Typography */
+:root {
+ --font-primary: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ --font-mono: 'JetBrains Mono', 'Courier New', monospace;
+
+ /* Font Sizes */
+ --text-xs: 0.75rem; /* 12px */
+ --text-sm: 0.875rem; /* 14px */
+ --text-base: 1rem; /* 16px */
+ --text-lg: 1.125rem; /* 18px */
+ --text-xl: 1.25rem; /* 20px */
+ --text-2xl: 1.5rem; /* 24px */
+ --text-3xl: 1.875rem; /* 30px */
+ --text-4xl: 2.25rem; /* 36px */
+ --text-5xl: 3rem; /* 48px */
+ --text-6xl: 3.75rem; /* 60px */
+
+ /* Font Weights */
+ --font-normal: 400;
+ --font-medium: 500;
+ --font-semibold: 600;
+ --font-bold: 700;
+ --font-extrabold: 800;
+
+ /* Spacing (8px base) */
+ --space-1: 0.5rem; /* 8px */
+ --space-2: 1rem; /* 16px */
+ --space-3: 1.5rem; /* 24px */
+ --space-4: 2rem; /* 32px */
+ --space-5: 2.5rem; /* 40px */
+ --space-6: 3rem; /* 48px */
+ --space-8: 4rem; /* 64px */
+ --space-10: 5rem; /* 80px */
+ --space-12: 6rem; /* 96px */
+ --space-16: 8rem; /* 128px */
+
+ /* Border Radius */
+ --radius-sm: 0.375rem; /* 6px */
+ --radius-md: 0.5rem; /* 8px */
+ --radius-lg: 0.75rem; /* 12px */
+ --radius-xl: 1rem; /* 16px */
+ --radius-2xl: 1.5rem; /* 24px */
+ --radius-full: 9999px;
+
+ /* Transitions */
+ --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
+ --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
+ --transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1);
+
+ /* Z-index */
+ --z-base: 1;
+ --z-dropdown: 100;
+ --z-sticky: 200;
+ --z-fixed: 300;
+ --z-modal: 400;
+ --z-popover: 500;
+ --z-tooltip: 600;
+
+ /* Container */
+ --container-max: 1280px;
+ --container-padding: var(--space-4);
+}
+
+/* Responsive Typography */
+@media (max-width: 768px) {
+ :root {
+ --text-4xl: 1.875rem; /* 30px */
+ --text-5xl: 2.25rem; /* 36px */
+ --text-6xl: 2.5rem; /* 40px */
+ }
+}
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 000000000..a18220c49
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ base: '/',
+ build: {
+ outDir: 'dist',
+ assetsDir: 'assets',
+ sourcemap: false,
+ },
+})