77 < link rel ="icon " type ="image/svg+xml " href ="icon.svg " />
88 < link rel ="apple-touch-icon " href ="icon.svg " />
99 < meta name ="description " content ="Herramientas clínicas para Cirugía Ortopédica y Traumatología " />
10+ < meta name ="theme-color " id ="meta-theme " content ="#0f172a " />
11+
12+ <!-- Open Graph -->
13+ < meta property ="og:title " content ="Herramientas · jmacot " />
14+ < meta property ="og:description " content ="Aplicaciones web de uso clínico y administrativo para Cirugía Ortopédica y Traumatología " />
15+ < meta property ="og:type " content ="website " />
16+ < meta property ="og:url " content ="https://jmacot.github.io/ " />
17+ < meta property ="og:image " content ="https://jmacot.github.io/icon.svg " />
18+ < meta name ="twitter:card " content ="summary " />
19+
20+ <!-- Fonts -->
21+ < link rel ="preconnect " href ="https://fonts.googleapis.com " />
22+ < link rel ="preconnect " href ="https://fonts.gstatic.com " crossorigin />
1023 < link href ="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Mono:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap " rel ="stylesheet " />
1124 < style >
1225 : root {
185198 color : white;
186199 }
187200
201+ .filter-count {
202+ font-size : 9px ;
203+ font-weight : 600 ;
204+ opacity : 0.6 ;
205+ margin-left : 2px ;
206+ }
207+
208+ /* ── SEARCH ── */
209+ .search-wrap {
210+ position : relative;
211+ margin-bottom : 24px ;
212+ }
213+ .search-icon {
214+ position : absolute;
215+ left : 16px ; top : 50% ;
216+ transform : translateY (-50% );
217+ color : var (--ink-muted );
218+ pointer-events : none;
219+ }
220+ # buscar {
221+ width : 100% ;
222+ padding : 12px 16px 12px 44px ;
223+ font-family : 'DM Sans' , sans-serif;
224+ font-size : 14px ;
225+ color : var (--ink );
226+ background : var (--surface );
227+ border : 1.5px solid var (--border );
228+ border-radius : 999px ;
229+ outline : none;
230+ box-shadow : var (--shadow-sm );
231+ transition : border-color 0.2s , box-shadow 0.2s ;
232+ }
233+ # buscar : focus {
234+ border-color : var (--ink-muted );
235+ box-shadow : 0 0 0 3px rgb (15 23 42 / 0.06 );
236+ }
237+ # buscar ::placeholder { color : var (--ink-muted ); opacity : 0.6 ; }
238+
188239 .filter-btn [data-tag = "todos" ].active {
189240 background : var (--ink );
190241 border-color : var (--ink );
244295 display : none;
245296 }
246297
298+ .card .fading-out {
299+ opacity : 0 ;
300+ transform : translateY (8px ) scale (0.97 );
301+ }
302+
303+ .card .fading-in {
304+ opacity : 0 ;
305+ transform : translateY (8px );
306+ }
307+
247308 .card : hover {
248309 transform : translateY (-6px );
249310 box-shadow : var (--shadow-hover );
266327 display : flex;
267328 align-items : center;
268329 justify-content : center;
269- font-size : 22px ;
270330 }
331+ .card-icon svg { width : 22px ; height : 22px ; flex-shrink : 0 ; }
271332
272333 .card-meta {
273334 display : flex;
474535
475536 @media (max-width : 600px ) {
476537 header { padding : 40px 24px 36px ; }
538+ header ::before , header ::after { display : none; }
477539 main { padding : 40px 20px 60px ; }
478540 .grid { grid-template-columns : 1fr ; }
479541 .filters { gap : 6px ; }
480542 .filter-btn { font-size : 10px ; padding : 6px 12px ; }
481543 .theme-toggle { width : 34px ; height : 34px ; font-size : 1rem ; top : 10px ; right : 10px ; }
482544 }
545+
546+ @media (prefers-reduced-motion : reduce) {
547+ * , * ::before , * ::after {
548+ animation-duration : 0.01ms !important ;
549+ transition-duration : 0.01ms !important ;
550+ }
551+ }
483552 </ style >
484553</ head >
485554< body >
486555 < header >
487- < button class ="theme-toggle " id ="btn-theme " title ="Cambiar tema "> </ button >
556+ < button class ="theme-toggle " id ="btn-theme " title ="Cambiar tema " aria-label =" Cambiar a modo oscuro " > </ button >
488557 < div class ="header-label "> Herramientas clínicas</ div >
489558 < h1 > Herramientas< br > < em > para Cirugía Ortopédica y Traumatología</ em > </ h1 >
490559 < p > Aplicaciones web de uso clínico y administrativo para cirugía ortopédica.</ p >
@@ -507,12 +576,17 @@ <h1>Herramientas<br><em>para Cirugía Ortopédica y Traumatología</em></h1>
507576 < button class ="filter-btn " data-tag ="administracion " onclick ="filtrar('administracion') "> Administración</ button >
508577 </ div >
509578
579+ < div class ="search-wrap ">
580+ < svg class ="search-icon " width ="16 " height ="16 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2.5 " stroke-linecap ="round " stroke-linejoin ="round "> < circle cx ="11 " cy ="11 " r ="8 "/> < line x1 ="21 " y1 ="21 " x2 ="16.65 " y2 ="16.65 "/> </ svg >
581+ < input type ="search " id ="buscar " placeholder ="Buscar herramienta... " autocomplete ="off " />
582+ </ div >
583+
510584 < div class ="grid ">
511585
512586 <!-- PLANTILLAS QUIRÚRGICAS -->
513587 < a class ="card " data-tag ="quirofano " href ="https://jmacot.github.io/plantillas-qx " target ="_blank " style ="animation-delay: 0s ">
514588 < div class ="card-meta ">
515- < div class ="card-icon "> 📋 </ div >
589+ < div class ="card-icon "> < svg width =" 22 " height =" 22 " viewBox =" 0 0 24 24 " fill =" none " stroke =" var(--c-quirofano) " stroke-width =" 2 " stroke-linecap =" round " stroke-linejoin =" round " > < path d =" M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2 " /> < rect x =" 8 " y =" 2 " width =" 8 " height =" 4 " rx =" 1 " ry =" 1 " /> </ svg > </ div >
516590 < div class ="card-arrow ">
517591 < svg width ="18 " height ="18 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 "> < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2.5 " d ="M14 5l7 7m0 0l-7 7m7-7H3 "/> </ svg >
518592 </ div >
@@ -527,7 +601,7 @@ <h2>Plantillas Quirúrgicas COT</h2>
527601 <!-- PROTOCOLOS REHABILITACION -->
528602 < a class ="card " data-tag ="pacientes " href ="https://jmacot.github.io/rehabilitacion-cot " target ="_blank " style ="animation-delay: 0.05s ">
529603 < div class ="card-meta ">
530- < div class ="card-icon " style =" --card-icon-bg: var(--c-pacientes-light) " >
604+ < div class ="card-icon ">
531605 < svg width ="22 " height ="22 " viewBox ="0 0 24 24 " fill ="none " stroke ="var(--c-pacientes) " stroke-width ="2 " stroke-linecap ="round " stroke-linejoin ="round "> < path d ="M18 20V10 "/> < path d ="M12 20V4 "/> < path d ="M6 20v-6 "/> </ svg >
532606 </ div >
533607 < div class ="card-arrow ">
@@ -544,7 +618,7 @@ <h2>Protocolos y Ejercicios</h2>
544618 <!-- KNEE ALIGN -->
545619 < a class ="card " data-tag ="planificacion " href ="https://jmacot.github.io/knee-align " target ="_blank " style ="animation-delay: 0.1s ">
546620 < div class ="card-meta ">
547- < div class ="card-icon "> 📐 </ div >
621+ < div class ="card-icon "> < svg width =" 22 " height =" 22 " viewBox =" 0 0 24 24 " fill =" none " stroke =" var(--c-planificacion) " stroke-width =" 2 " stroke-linecap =" round " stroke-linejoin =" round " > < path d =" M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z " /> < polyline points =" 3.27 6.96 12 12.01 20.73 6.96 " /> < line x1 =" 12 " y1 =" 22.08 " x2 =" 12 " y2 =" 12 " /> </ svg > </ div >
548622 < div class ="card-arrow ">
549623 < svg width ="18 " height ="18 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 "> < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2.5 " d ="M14 5l7 7m0 0l-7 7m7-7H3 "/> </ svg >
550624 </ div >
@@ -559,7 +633,7 @@ <h2>Knee Align</h2>
559633 <!-- PLANNING COT -->
560634 < a class ="card " data-tag ="administracion " href ="https://jmacot.github.io/planning-cot " target ="_blank " style ="animation-delay: 0.15s ">
561635 < div class ="card-meta ">
562- < div class ="card-icon "> 📊 </ div >
636+ < div class ="card-icon "> < svg width =" 22 " height =" 22 " viewBox =" 0 0 24 24 " fill =" none " stroke =" var(--c-administracion) " stroke-width =" 2 " stroke-linecap =" round " stroke-linejoin =" round " > < rect x =" 3 " y =" 3 " width =" 18 " height =" 18 " rx =" 2 " /> < path d =" M8 16V12 " /> < path d =" M12 16V8 " /> < path d =" M16 16v-2 " /> </ svg > </ div >
563637 < div class ="card-arrow ">
564638 < svg width ="18 " height ="18 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 "> < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2.5 " d ="M14 5l7 7m0 0l-7 7m7-7H3 "/> </ svg >
565639 </ div >
@@ -574,7 +648,7 @@ <h2>Analizador de Planning</h2>
574648 <!-- MATERIAL EXTERNO -->
575649 < a class ="card " data-tag ="documentacion " href ="https://jmacot.github.io/Material-Externo " target ="_blank " style ="animation-delay: 0.2s ">
576650 < div class ="card-meta ">
577- < div class ="card-icon "> 🔩 </ div >
651+ < div class ="card-icon "> < svg width =" 22 " height =" 22 " viewBox =" 0 0 24 24 " fill =" none " stroke =" var(--c-documentacion) " stroke-width =" 2 " stroke-linecap =" round " stroke-linejoin =" round " > < path d =" M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z " /> </ svg > </ div >
578652 < div class ="card-arrow ">
579653 < svg width ="18 " height ="18 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 "> < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2.5 " d ="M14 5l7 7m0 0l-7 7m7-7H3 "/> </ svg >
580654 </ div >
@@ -589,7 +663,7 @@ <h2>Solicitud de Material Externo</h2>
589663 <!-- CALCULADORA GUARDIAS -->
590664 < a class ="card " data-tag ="administracion " href ="https://jmacot.github.io/calculadora-guardias " target ="_blank " style ="animation-delay: 0.25s ">
591665 < div class ="card-meta ">
592- < div class ="card-icon "> 🏥 </ div >
666+ < div class ="card-icon "> < svg width =" 22 " height =" 22 " viewBox =" 0 0 24 24 " fill =" none " stroke =" var(--c-administracion) " stroke-width =" 2 " stroke-linecap =" round " stroke-linejoin =" round " > < path d =" M3 21h18 " /> < path d =" M5 21V7l8-4v18 " /> < path d =" M19 21V11l-6-4 " /> < path d =" M9 9h1 " /> < path d =" M9 13h1 " /> < path d =" M9 17h1 " /> </ svg > </ div >
593667 < div class ="card-arrow ">
594668 < svg width ="18 " height ="18 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 "> < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2.5 " d ="M14 5l7 7m0 0l-7 7m7-7H3 "/> </ svg >
595669 </ div >
@@ -604,7 +678,7 @@ <h2>Calculadora de Guardias</h2>
604678 <!-- CONSENTIMIENTOS INFORMADOS -->
605679 < a class ="card " data-tag ="documentacion " href ="https://jmacot.github.io/consentimientos " target ="_blank " style ="animation-delay: 0.3s ">
606680 < div class ="card-meta ">
607- < div class ="card-icon "> 📝 </ div >
681+ < div class ="card-icon "> < svg width =" 22 " height =" 22 " viewBox =" 0 0 24 24 " fill =" none " stroke =" var(--c-documentacion) " stroke-width =" 2 " stroke-linecap =" round " stroke-linejoin =" round " > < path d =" M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z " /> < polyline points =" 14 2 14 8 20 8 " /> < line x1 =" 16 " y1 =" 13 " x2 =" 8 " y2 =" 13 " /> < line x1 =" 16 " y1 =" 17 " x2 =" 8 " y2 =" 17 " /> < polyline points =" 10 9 9 9 8 9 " /> </ svg > </ div >
608682 < div class ="card-arrow ">
609683 < svg width ="18 " height ="18 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 "> < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2.5 " d ="M14 5l7 7m0 0l-7 7m7-7H3 "/> </ svg >
610684 </ div >
@@ -619,7 +693,7 @@ <h2>Consentimientos Informados</h2>
619693 <!-- CPAK PLANNER -->
620694 < a class ="card " data-tag ="planificacion " href ="https://jmacot.github.io/CPAK " target ="_blank " style ="animation-delay: 0.35s ">
621695 < div class ="card-meta ">
622- < div class ="card-icon "> 🦴 </ div >
696+ < div class ="card-icon "> < svg width =" 22 " height =" 22 " viewBox =" 0 0 24 24 " fill =" none " stroke =" var(--c-planificacion) " stroke-width =" 2 " stroke-linecap =" round " stroke-linejoin =" round " > < path d =" M22 12h-4l-3 9L9 3l-3 9H2 " /> </ svg > </ div >
623697 < div class ="card-arrow ">
624698 < svg width ="18 " height ="18 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 "> < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2.5 " d ="M14 5l7 7m0 0l-7 7m7-7H3 "/> </ svg >
625699 </ div >
@@ -631,25 +705,10 @@ <h2>CPAK Planner</h2>
631705 </ div >
632706 </ a >
633707
634- <!-- DEFORMIDADES CORONALES -->
635- < a class ="card " data-tag ="planificacion " href ="https://jmacot.github.io/deformidad-coronal " target ="_blank " style ="animation-delay: 0.35s ">
636- < div class ="card-meta ">
637- < div class ="card-icon "> 📐</ div >
638- < div class ="card-arrow ">
639- < svg width ="18 " height ="18 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 "> < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2.5 " d ="M14 5l7 7m0 0l-7 7m7-7H3 "/> </ svg >
640- </ div >
641- </ div >
642- < div class ="card-content ">
643- < div class ="card-tag "> Planificación</ div >
644- < h2 > Deformidades Coronales</ h2 >
645- < p > Analisis de alineacion coronal del miembro inferior con el algoritmo AKUMA. Calcula componentes de deformidad, diagnostico diferencial (HTO/DFO/DLO/UKA) y planificacion de correccion.</ p >
646- </ div >
647- </ a >
648-
649708 <!-- PLACEHOLDER -->
650709 < div class ="card placeholder " data-tag ="none " style ="animation-delay: 0.45s ">
651710 < div class ="card-meta ">
652- < div class ="card-icon "> + </ div >
711+ < div class ="card-icon "> < svg width =" 22 " height =" 22 " viewBox =" 0 0 24 24 " fill =" none " stroke =" currentColor " stroke-width =" 2 " stroke-linecap =" round " stroke-linejoin =" round " > < line x1 =" 12 " y1 =" 5 " x2 =" 12 " y2 =" 19 " /> < line x1 =" 5 " y1 =" 12 " x2 =" 19 " y2 =" 12 " /> </ svg > </ div >
653712 </ div >
654713 < div class ="card-content ">
655714 < div class ="card-tag "> En proceso</ div >
@@ -669,32 +728,92 @@ <h2>Próximamente</h2>
669728 // ─────────────────────────────────────────────
670729 // FILTROS
671730 // ─────────────────────────────────────────────
731+ const allCards = document . querySelectorAll ( '.card:not(.placeholder)' ) ;
732+
733+ let filterTimer ;
672734 function filtrar ( tag ) {
735+ clearTimeout ( filterTimer ) ;
736+ document . getElementById ( 'buscar' ) . value = '' ;
673737 document . querySelectorAll ( '.filter-btn' ) . forEach ( btn => {
674738 btn . classList . toggle ( 'active' , btn . dataset . tag === tag ) ;
675739 } ) ;
676- document . querySelectorAll ( '.card' ) . forEach ( card => {
677- if ( tag === 'todos' ) {
678- card . classList . remove ( 'hidden' ) ;
679- } else {
680- const cardTag = card . dataset . tag ;
681- card . classList . toggle ( 'hidden' , cardTag !== tag && cardTag !== 'none' ) ;
740+ const cards = document . querySelectorAll ( '.card' ) ;
741+ // Fade out cards that will hide
742+ cards . forEach ( card => {
743+ const cardTag = card . dataset . tag ;
744+ const shouldShow = tag === 'todos' || cardTag === tag || cardTag === 'none' ;
745+ if ( ! shouldShow && ! card . classList . contains ( 'hidden' ) ) {
746+ card . classList . add ( 'fading-out' ) ;
682747 }
683748 } ) ;
749+ // After fade out, toggle display and fade in new ones
750+ filterTimer = setTimeout ( ( ) => {
751+ cards . forEach ( card => {
752+ const cardTag = card . dataset . tag ;
753+ const shouldShow = tag === 'todos' || cardTag === tag || cardTag === 'none' ;
754+ card . classList . remove ( 'fading-out' ) ;
755+ if ( shouldShow ) {
756+ card . classList . remove ( 'hidden' ) ;
757+ card . classList . add ( 'fading-in' ) ;
758+ requestAnimationFrame ( ( ) => requestAnimationFrame ( ( ) => {
759+ card . classList . remove ( 'fading-in' ) ;
760+ } ) ) ;
761+ } else {
762+ card . classList . add ( 'hidden' ) ;
763+ }
764+ } ) ;
765+ } , 200 ) ;
766+ }
767+
768+ // Inject count badges
769+ ( function initCounts ( ) {
770+ document . querySelectorAll ( '.filter-btn' ) . forEach ( btn => {
771+ const tag = btn . dataset . tag ;
772+ const count = tag === 'todos'
773+ ? allCards . length
774+ : document . querySelectorAll ( `.card[data-tag="${ tag } "]:not(.placeholder)` ) . length ;
775+ btn . insertAdjacentHTML ( 'beforeend' , ` <span class="filter-count">${ count } </span>` ) ;
776+ } ) ;
777+ } ) ( ) ;
778+
779+ // ─────────────────────────────────────────────
780+ // BUSQUEDA
781+ // ─────────────────────────────────────────────
782+ function normalizar ( str ) {
783+ return str . toLowerCase ( ) . normalize ( 'NFD' ) . replace ( / [ \u0300 - \u036f ] / g, '' ) ;
684784 }
685785
786+ const inputBuscar = document . getElementById ( 'buscar' ) ;
787+ inputBuscar . addEventListener ( 'input' , ( ) => {
788+ const q = normalizar ( inputBuscar . value . trim ( ) ) ;
789+ const activeTag = document . querySelector ( '.filter-btn.active' ) ?. dataset . tag || 'todos' ;
790+ document . querySelectorAll ( '.card' ) . forEach ( card => {
791+ if ( card . classList . contains ( 'placeholder' ) ) return ;
792+ const title = normalizar ( card . querySelector ( 'h2' ) ?. textContent || '' ) ;
793+ const desc = normalizar ( card . querySelector ( 'p' ) ?. textContent || '' ) ;
794+ const matchSearch = ! q || title . includes ( q ) || desc . includes ( q ) ;
795+ const matchTag = activeTag === 'todos' || card . dataset . tag === activeTag ;
796+ card . classList . toggle ( 'hidden' , ! ( matchSearch && matchTag ) ) ;
797+ } ) ;
798+ } ) ;
799+
686800 // ─────────────────────────────────────────────
687801 // DARK MODE
688802 // ─────────────────────────────────────────────
689803 const btnTheme = document . getElementById ( 'btn-theme' ) ;
690804
691805 function applyTheme ( dark ) {
806+ const metaTheme = document . getElementById ( 'meta-theme' ) ;
692807 if ( dark ) {
693808 document . documentElement . setAttribute ( 'data-theme' , 'dark' ) ;
694809 btnTheme . textContent = '\u2600\uFE0F' ;
810+ btnTheme . setAttribute ( 'aria-label' , 'Cambiar a modo claro' ) ;
811+ metaTheme . setAttribute ( 'content' , '#152238' ) ;
695812 } else {
696813 document . documentElement . removeAttribute ( 'data-theme' ) ;
697814 btnTheme . textContent = '\uD83C\uDF19' ;
815+ btnTheme . setAttribute ( 'aria-label' , 'Cambiar a modo oscuro' ) ;
816+ metaTheme . setAttribute ( 'content' , '#0f172a' ) ;
698817 }
699818 }
700819
0 commit comments