splent-feature-team 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. splent_feature_team-0.3.2.dist-info/METADATA +5 -0
  2. splent_feature_team-0.3.2.dist-info/RECORD +76 -0
  3. splent_feature_team-0.3.2.dist-info/WHEEL +5 -0
  4. splent_feature_team-0.3.2.dist-info/top_level.txt +1 -0
  5. splent_io/splent_feature_team/__init__.py +25 -0
  6. splent_io/splent_feature_team/assets/css/team.css +138 -0
  7. splent_io/splent_feature_team/assets/dist/splent_feature_team.bundle.js +20 -0
  8. splent_io/splent_feature_team/assets/dist/splent_feature_team.bundle.js.map +1 -0
  9. splent_io/splent_feature_team/assets/js/scripts.js +12 -0
  10. splent_io/splent_feature_team/assets/js/webpack.config.js +14 -0
  11. splent_io/splent_feature_team/commands.py +21 -0
  12. splent_io/splent_feature_team/config.py +19 -0
  13. splent_io/splent_feature_team/forms.py +6 -0
  14. splent_io/splent_feature_team/hooks.py +23 -0
  15. splent_io/splent_feature_team/migrations/alembic.ini +36 -0
  16. splent_io/splent_feature_team/migrations/env.py +9 -0
  17. splent_io/splent_feature_team/migrations/script.py.mako +24 -0
  18. splent_io/splent_feature_team/migrations/versions/1bb39441d98d_splent_feature_team.py +46 -0
  19. splent_io/splent_feature_team/migrations/versions/3f76830b5164_splent_feature_team.py +29 -0
  20. splent_io/splent_feature_team/migrations/versions/tm0003_roles_m2m.py +101 -0
  21. splent_io/splent_feature_team/models.py +73 -0
  22. splent_io/splent_feature_team/repositories.py +38 -0
  23. splent_io/splent_feature_team/routes.py +203 -0
  24. splent_io/splent_feature_team/seed_media/alberto-valenzuela-munoz.jpg +0 -0
  25. splent_io/splent_feature_team/seed_media/ana-eva-chacon-luna.jpg +0 -0
  26. splent_io/splent_feature_team/seed_media/antonio-german-marquez-trujillo.jpg +0 -0
  27. splent_io/splent_feature_team/seed_media/antonio-j-dominguez.jpg +0 -0
  28. splent_io/splent_feature_team/seed_media/antonio-jimenez-ortega.jpg +0 -0
  29. splent_io/splent_feature_team/seed_media/antonio-manuel-guiterrez-fernandez.webp +0 -0
  30. splent_io/splent_feature_team/seed_media/asmaa-hasan-sbaih.jpg +0 -0
  31. splent_io/splent_feature_team/seed_media/cristian-vidal-silva.jpg +0 -0
  32. splent_io/splent_feature_team/seed_media/daniel-molina-torres.jpg +0 -0
  33. splent_io/splent_feature_team/seed_media/david-benavides.jpg +0 -0
  34. splent_io/splent_feature_team/seed_media/david-romero-organvidez.jpg +0 -0
  35. splent_io/splent_feature_team/seed_media/esther-rocio-valladolid-ortiz.jpg +0 -0
  36. splent_io/splent_feature_team/seed_media/f-j-dominguez-mayo.jpg +0 -0
  37. splent_io/splent_feature_team/seed_media/felipe-escalera-gonzalez.jpg +0 -0
  38. splent_io/splent_feature_team/seed_media/francisco-sebastian-benitez-ruis-diaz.jpg +0 -0
  39. splent_io/splent_feature_team/seed_media/guillermo-ciria-gonzalez.jpg +0 -0
  40. splent_io/splent_feature_team/seed_media/isabel-mylonas.jpg +0 -0
  41. splent_io/splent_feature_team/seed_media/javier-aponte.jpg +0 -0
  42. splent_io/splent_feature_team/seed_media/jesennia-cardenas-cobo.jpg +0 -0
  43. splent_io/splent_feature_team/seed_media/jesus-fernandez-romero.jpg +0 -0
  44. splent_io/splent_feature_team/seed_media/jesus-moreno-leon.jpg +0 -0
  45. splent_io/splent_feature_team/seed_media/jorge-garcia.jpg +0 -0
  46. splent_io/splent_feature_team/seed_media/jorge-rodas-silva.jpg +0 -0
  47. splent_io/splent_feature_team/seed_media/jose-a-galindo.jpg +0 -0
  48. splent_io/splent_feature_team/seed_media/jose-a-zamudio.jpg +0 -0
  49. splent_io/splent_feature_team/seed_media/jose-manuel-sanchez-ruiz.jpg +0 -0
  50. splent_io/splent_feature_team/seed_media/jose-miguel-horcas.webp +0 -0
  51. splent_io/splent_feature_team/seed_media/julio-navarro-rodriguez.jpg +0 -0
  52. splent_io/splent_feature_team/seed_media/mario-rivas-becerra.jpg +0 -0
  53. splent_io/splent_feature_team/seed_media/mariuxi-vinueza-morales.jpg +0 -0
  54. splent_io/splent_feature_team/seed_media/megha-bhushan.jpg +0 -0
  55. splent_io/splent_feature_team/seed_media/miguel-moreno-martin-palomo.png +0 -0
  56. splent_io/splent_feature_team/seed_media/miguel-olivero.jpg +0 -0
  57. splent_io/splent_feature_team/seed_media/noelia-lopez-duran.jpg +0 -0
  58. splent_io/splent_feature_team/seed_media/pablo-neira.png +0 -0
  59. splent_io/splent_feature_team/seed_media/pablo-pazo.jpg +0 -0
  60. splent_io/splent_feature_team/seed_media/pablo-ramos-berciano.jpg +0 -0
  61. splent_io/splent_feature_team/seed_media/rafael-harana-mancilla.png +0 -0
  62. splent_io/splent_feature_team/seed_media/salvador-munoz-hermoso.jpg +0 -0
  63. splent_io/splent_feature_team/seed_media/victor-ramirez-de-la-corte.jpg +0 -0
  64. splent_io/splent_feature_team/seed_media/victor-rea-sanchez.jpg +0 -0
  65. splent_io/splent_feature_team/seeders.py +621 -0
  66. splent_io/splent_feature_team/services.py +17 -0
  67. splent_io/splent_feature_team/signals.py +19 -0
  68. splent_io/splent_feature_team/templates/team/admin/form.html +81 -0
  69. splent_io/splent_feature_team/templates/team/admin/list.html +88 -0
  70. splent_io/splent_feature_team/templates/team/admin/roles.html +82 -0
  71. splent_io/splent_feature_team/templates/team/detail.html +44 -0
  72. splent_io/splent_feature_team/templates/team/index.html +10 -0
  73. splent_io/splent_feature_team/templates/team/list.html +33 -0
  74. splent_io/splent_feature_team/translations/es/LC_MESSAGES/messages.mo +0 -0
  75. splent_io/splent_feature_team/translations/es/LC_MESSAGES/messages.po +44 -0
  76. splent_io/splent_feature_team/translations/messages.pot +43 -0
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: splent_feature_team
3
+ Version: 0.3.2
4
+ Requires-Python: >=3.13
5
+ Description-Content-Type: text/markdown
@@ -0,0 +1,76 @@
1
+ splent_io/splent_feature_team/__init__.py,sha256=GHz6CPfg0JgV3U5hqvKsn7C2CWYROj0XKvxAPeKxKKQ,981
2
+ splent_io/splent_feature_team/commands.py,sha256=TDY_lKoiLAwWpBQ4Fs53XpwvuxEhu13gkffS8DoaGk0,397
3
+ splent_io/splent_feature_team/config.py,sha256=RRJ50csrKYPEVHIgo_w9LcrlXWW2rBRvQ2hahAt_Ptc,490
4
+ splent_io/splent_feature_team/forms.py,sha256=dmFKZ8mPTqq-B0BSC_pHaKzc7ACHnj4Cj2C99FC6DLI,159
5
+ splent_io/splent_feature_team/hooks.py,sha256=AVmmFUdN5o8GS_7wguLtHaeUNEffAKmYpAsDqgIT0nU,695
6
+ splent_io/splent_feature_team/models.py,sha256=qZznRHGLtQw2gcr_C5eYiuLWoS1yHIqYgVLRH6M7LLE,2368
7
+ splent_io/splent_feature_team/repositories.py,sha256=TtFKmzv8Ah5ukLp0GKuZG9llHRizZROd8HV-Q43VNhQ,1320
8
+ splent_io/splent_feature_team/routes.py,sha256=geOmGxgiGwwiqAdBYEySud6RabPzrRVVRVIIrMJNzoc,6822
9
+ splent_io/splent_feature_team/seeders.py,sha256=e7N_wUwjNNT0H3cZP0-obmKFhWw7C3pMYVJErng7QvE,21572
10
+ splent_io/splent_feature_team/services.py,sha256=J2_4F9qbB-qZTo2KVv5jfEHhnBcvdHalLPauBF8nfoc,533
11
+ splent_io/splent_feature_team/signals.py,sha256=6mwSyQACO9TqimiieW5XFndJpfWr2Siul98tLyatp9I,614
12
+ splent_io/splent_feature_team/assets/css/team.css,sha256=7DuDbphw-dl6UfxZzuQdx7ryveqwotPJbHetz9rdsW4,3556
13
+ splent_io/splent_feature_team/assets/dist/splent_feature_team.bundle.js,sha256=iTlwEWSE6XBc0DdLqTa4x3elMDE3X5RCokx0yiJ0JiI,953
14
+ splent_io/splent_feature_team/assets/dist/splent_feature_team.bundle.js.map,sha256=c8DIeC2KVRS4puUTCw3uqv2XAueIE7_-w1H2Qoq2PSk,889
15
+ splent_io/splent_feature_team/assets/js/scripts.js,sha256=rerxKTWA2ocP3H10PEKM7eVbvDOhn_vL0FiQAsu2nlQ,572
16
+ splent_io/splent_feature_team/assets/js/webpack.config.js,sha256=BUgdzxrJU-B6w4Jxa4kRlc9b5GP34y8ebYXfMpaDrmc,276
17
+ splent_io/splent_feature_team/migrations/alembic.ini,sha256=WA1biGHSZpXO-M7uFtAZVRSV5Fih0VIQY-Yp7G7xGTk,517
18
+ splent_io/splent_feature_team/migrations/env.py,sha256=xvNQG5cNrng00tWdIdhhk5K7ZbNsulXjubldm80xd1I,309
19
+ splent_io/splent_feature_team/migrations/script.py.mako,sha256=8_xgA-gm_OhehnO7CiIijWgnm00ZlszEHtIHrAYFJl0,494
20
+ splent_io/splent_feature_team/migrations/versions/1bb39441d98d_splent_feature_team.py,sha256=4qnT2X_l9KsX5BonOnHCok8cijt52kLVXNgsu7VUKSk,1558
21
+ splent_io/splent_feature_team/migrations/versions/3f76830b5164_splent_feature_team.py,sha256=idMokVtyuPIFjFgg6oaIAQ2fUpz1DgcSsrmLvL9tOHA,664
22
+ splent_io/splent_feature_team/migrations/versions/tm0003_roles_m2m.py,sha256=t_yh-5kPg_DZP6Fx4OiG1BCMgBybuA2Nu18cjgZwESc,3400
23
+ splent_io/splent_feature_team/seed_media/alberto-valenzuela-munoz.jpg,sha256=bE8wcwYKjj9wfeIpXCmv2A0tOIGwuLYJ-zPPKQj5e7A,201708
24
+ splent_io/splent_feature_team/seed_media/ana-eva-chacon-luna.jpg,sha256=UUDFt5Z6ruVoEG-2He5JN7GjcqLTWNUH_hnu46bNCZI,5849
25
+ splent_io/splent_feature_team/seed_media/antonio-german-marquez-trujillo.jpg,sha256=YXZuTzC1GDQwdCUmKNFeJceoly03-I4Dral33r29CbA,14088
26
+ splent_io/splent_feature_team/seed_media/antonio-j-dominguez.jpg,sha256=PkFgzBbWgDqGy0YoY_x02oDScRcf_XX-u3dUQdLICWA,5156
27
+ splent_io/splent_feature_team/seed_media/antonio-jimenez-ortega.jpg,sha256=iEyfpCl9kYCxPBud5Qb5PI6Xxe42_pppNdN030Hr9L0,16501
28
+ splent_io/splent_feature_team/seed_media/antonio-manuel-guiterrez-fernandez.webp,sha256=Hbs0Ng1j2O79uAXwot-vlVPjrPFPHq5dC4lPpVk2Eq8,2564
29
+ splent_io/splent_feature_team/seed_media/asmaa-hasan-sbaih.jpg,sha256=IHW38fQU__Lyseoa35embYJuMjBQHzjtYkTeSEZQhKc,134605
30
+ splent_io/splent_feature_team/seed_media/cristian-vidal-silva.jpg,sha256=RtOPvxVa9nV0LcuRGsz7jmUqA7V8WRKq8OpQN4i4JJk,4660
31
+ splent_io/splent_feature_team/seed_media/daniel-molina-torres.jpg,sha256=vuhyqk6I99bOci79Bse7Mo8VtXh8JkFmOPdrc4mySyI,306309
32
+ splent_io/splent_feature_team/seed_media/david-benavides.jpg,sha256=BkkTlbih9VlG8M3h3a86WWqRJBnu-J6Ya7XK0Z5KTag,12508
33
+ splent_io/splent_feature_team/seed_media/david-romero-organvidez.jpg,sha256=w2ZzurNN6lHp0oLyirczrtFmLFDqGZagZekEa4Ho8tI,4955
34
+ splent_io/splent_feature_team/seed_media/esther-rocio-valladolid-ortiz.jpg,sha256=VJoZyM6oTah9EPP46Bt7T1jhfw7XrET7dCkVC8ugCcQ,119026
35
+ splent_io/splent_feature_team/seed_media/f-j-dominguez-mayo.jpg,sha256=Bi19VG4_uItU-2ZzCuy79LHs6Nup2GuUOQ3958bkxdw,11135
36
+ splent_io/splent_feature_team/seed_media/felipe-escalera-gonzalez.jpg,sha256=ObOG3AJZ57lDEubehcvzltW4XafrQSi5XYhsptY66fo,3917
37
+ splent_io/splent_feature_team/seed_media/francisco-sebastian-benitez-ruis-diaz.jpg,sha256=QgmrO4D0ypzxIbBX8jZEj0Z0WtfgkYeCvWex2Jn2_WU,16141
38
+ splent_io/splent_feature_team/seed_media/guillermo-ciria-gonzalez.jpg,sha256=rhoHywfXT77kXucBderhSciS1gtiH0dMPIlU9tGfwZM,3093
39
+ splent_io/splent_feature_team/seed_media/isabel-mylonas.jpg,sha256=LrUlFIVAKqNIFwfC2UoFhZ1t-AZ9mNVvuOFFqDZKq_Y,12638
40
+ splent_io/splent_feature_team/seed_media/javier-aponte.jpg,sha256=Khx0dYcESTnDB5UMLA8GJupbkm2p-QwZrb73MXWH2jY,4204
41
+ splent_io/splent_feature_team/seed_media/jesennia-cardenas-cobo.jpg,sha256=UDTFqOLNnmU0gctyB4jZYvCdjULrjOK-PKzQyO7gT88,11858
42
+ splent_io/splent_feature_team/seed_media/jesus-fernandez-romero.jpg,sha256=OScRkt9vT8z5TiP8Gy_oBEEIO7M9U2mv0-HI02qdWFw,420890
43
+ splent_io/splent_feature_team/seed_media/jesus-moreno-leon.jpg,sha256=85XzEpctmT5A6S3q0S1jUq29a5ol78pRsnJWPc0SLKI,13708
44
+ splent_io/splent_feature_team/seed_media/jorge-garcia.jpg,sha256=52My5dF2ijiAwf1rCQMnqFcuYFNMbMPq9XHA4kJcGWk,10588
45
+ splent_io/splent_feature_team/seed_media/jorge-rodas-silva.jpg,sha256=kVfTkdn7NhTtRaX6JbYp1hTaKSoLgFUB6xY4bVrJhOw,13497
46
+ splent_io/splent_feature_team/seed_media/jose-a-galindo.jpg,sha256=AHGgQL7GsXowl_xgSiwoItwgTzcniQ0Kk-O_vK_0Uj8,11258
47
+ splent_io/splent_feature_team/seed_media/jose-a-zamudio.jpg,sha256=8Z6NSUusmFaKkCdWOgBz2j0SiLrcSqd3rlUSLWOrK-4,19477
48
+ splent_io/splent_feature_team/seed_media/jose-manuel-sanchez-ruiz.jpg,sha256=bIpRV4Uz8PcaSKQuXczGSUlApL5ZuHCLWM_xswEJVCw,15639
49
+ splent_io/splent_feature_team/seed_media/jose-miguel-horcas.webp,sha256=JTASbnpwoFPAqxtiK47-SIu3mzT5vadnnY7nKUTxlOM,5130
50
+ splent_io/splent_feature_team/seed_media/julio-navarro-rodriguez.jpg,sha256=IYF7hM-84oxlqnJNfUwbR7FLkyyoJMlxW2ih_TydRUA,17263
51
+ splent_io/splent_feature_team/seed_media/mario-rivas-becerra.jpg,sha256=XQcLTsWAvqHkIV47j_qbuJF2b9otJzRNe2HzIdY-sro,144504
52
+ splent_io/splent_feature_team/seed_media/mariuxi-vinueza-morales.jpg,sha256=ikJuTbh5oCa7nyhVT11bQNpUL3KuTwqOtEnm-NAFtmE,4975
53
+ splent_io/splent_feature_team/seed_media/megha-bhushan.jpg,sha256=U4D-qzwhPGW5ZiQK-O2PQ26O2GVlCy5wLLVOlU43CaY,5802
54
+ splent_io/splent_feature_team/seed_media/miguel-moreno-martin-palomo.png,sha256=MODRLTriRap3XpLxQBVgreZjNEBw0xrJqdPZsNLOG9U,29371
55
+ splent_io/splent_feature_team/seed_media/miguel-olivero.jpg,sha256=z8NAadZ3UOF1Txb0y88vDj7zegLo3_ED5UkSTmLehjg,10168
56
+ splent_io/splent_feature_team/seed_media/noelia-lopez-duran.jpg,sha256=EX-Gsmmvx6cP3qWcrhnitxXiE3NQUUCoxzI_qDMrUzU,5074
57
+ splent_io/splent_feature_team/seed_media/pablo-neira.png,sha256=KqRc5FeOuOMik3LSUd_M1GgwAnf7H7vxwOTrezFIU68,41385
58
+ splent_io/splent_feature_team/seed_media/pablo-pazo.jpg,sha256=-MPY46cZwC9TermAlIrlDBfia6PDvUcWVU04MuE8I1c,4927
59
+ splent_io/splent_feature_team/seed_media/pablo-ramos-berciano.jpg,sha256=ZygbqcZzvrXLRryYTslYMUdtO1RjugF97IS8sG7zoBE,9493
60
+ splent_io/splent_feature_team/seed_media/rafael-harana-mancilla.png,sha256=5XOzWcbL9T8A15NbrUznP28NSTCXGRYuuXRpRsod-bo,114194
61
+ splent_io/splent_feature_team/seed_media/salvador-munoz-hermoso.jpg,sha256=omzXvQQsBrLZtJtN8SK28tW9NIBM0wB6ZOn1dpq6JTk,3507
62
+ splent_io/splent_feature_team/seed_media/victor-ramirez-de-la-corte.jpg,sha256=b1sgr5R0C_U_Go5W6x6HE-CUvCDHDKJL2Yc71kPFky8,16733
63
+ splent_io/splent_feature_team/seed_media/victor-rea-sanchez.jpg,sha256=ZDyfaL8p-GcBAc9aqd6a20btyHHtLGPV45tjUQxLeRw,4673
64
+ splent_io/splent_feature_team/templates/team/detail.html,sha256=HFK8CESYb2Zo72NAfSlVFZ8AAmzHpDYi89t7DbAGFAs,1667
65
+ splent_io/splent_feature_team/templates/team/index.html,sha256=t_qlxsGAeBtisXLY4mV8uJqM5-j9nK5EeRIKi8GHyo8,145
66
+ splent_io/splent_feature_team/templates/team/list.html,sha256=YGw_rZ8SuRWXlN8xPXJghnLotVEG09LaUw_bDXvFiPY,1271
67
+ splent_io/splent_feature_team/templates/team/admin/form.html,sha256=JZ8XwpzHQH9sAvDTr0TqlB2adg7TY8N87E4lFeH6_yI,4396
68
+ splent_io/splent_feature_team/templates/team/admin/list.html,sha256=mqElUq2uN0pP1PowZQHhLcsVDuWwaTrHPWJVjIzVodE,3901
69
+ splent_io/splent_feature_team/templates/team/admin/roles.html,sha256=kLyVTmWdQho39SrftJQ8D019dEhh0ORgm-Nssk6118E,3972
70
+ splent_io/splent_feature_team/translations/messages.pot,sha256=UpFhQuKjeUXJkjLXwhg2uHLQIKOg3hHaCLxzBStCWOQ,1208
71
+ splent_io/splent_feature_team/translations/es/LC_MESSAGES/messages.mo,sha256=UmZXYuA1uffqk-Mzg6bTzMJtdZ3C82VPM2CXt-Rimko,774
72
+ splent_io/splent_feature_team/translations/es/LC_MESSAGES/messages.po,sha256=yBD3Xfg9DsRfousqOrV13WD8tpuYcQ1En1DYdnJyufQ,1383
73
+ splent_feature_team-0.3.2.dist-info/METADATA,sha256=W0bvYezGa28CQ_jdvkdD_fOxY_hSy9jO7bP6rAIb3Fw,127
74
+ splent_feature_team-0.3.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
75
+ splent_feature_team-0.3.2.dist-info/top_level.txt,sha256=nksrfdr_edPhGPUsCqz08kyNRLBkNc2sInZYzmbZ4TA,10
76
+ splent_feature_team-0.3.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ splent_io
@@ -0,0 +1,25 @@
1
+ from splent_framework.blueprints.base_blueprint import create_blueprint
2
+ from splent_framework.nav.nav_registry import register_nav_item
3
+ from splent_framework.services.service_locator import register_service
4
+
5
+ from splent_io.splent_feature_team.services import TeamService
6
+
7
+ team_bp = create_blueprint(__name__)
8
+
9
+
10
+ def init_feature(app):
11
+ from splent_framework.assets.asset_registry import register_asset
12
+
13
+ # Team is managed through its OWN custom admin screens (see routes.py and
14
+ # hooks.py) — the WordPress-plugin pattern — instead of the generic admin
15
+ # resource, so it does not call register_admin_resource.
16
+ register_service(app, "TeamService", TeamService)
17
+ register_nav_item(key="team", label="Team", href="/team", order=40)
18
+ # Public team-member detail stylesheet (token-driven; styled by the skin).
19
+ register_asset(
20
+ "css", "team.assets", order=100, subfolder="css", filename="team.css"
21
+ )
22
+
23
+
24
+ def inject_context_vars(app):
25
+ return {}
@@ -0,0 +1,138 @@
1
+ /* ══════════════════════════════════════════════════════════════════════════
2
+ splent_feature_team — public team-member detail sheet.
3
+ Token-driven: every colour/radius comes from the theme's --brand-* custom
4
+ properties, so a skin restyles it for free. Loaded via register_asset on top
5
+ of the theme's public.css. No inline styles, no CDN.
6
+ ══════════════════════════════════════════════════════════════════════════ */
7
+
8
+ .member-detail {
9
+ max-width: 72ch;
10
+ }
11
+
12
+ .member-detail__photo {
13
+ width: 160px;
14
+ height: 160px;
15
+ border-radius: 50%;
16
+ object-fit: cover;
17
+ margin-bottom: 1rem;
18
+ }
19
+
20
+ .member-roles {
21
+ display: flex;
22
+ flex-wrap: wrap;
23
+ gap: .35rem;
24
+ margin-bottom: 1rem;
25
+ }
26
+
27
+ .member-detail .section-title {
28
+ margin-top: 1.5rem;
29
+ }
30
+
31
+ .member-links {
32
+ display: flex;
33
+ flex-wrap: wrap;
34
+ gap: .5rem;
35
+ }
36
+
37
+ /* ── Email contact row: text label / small mail icon, no emoji ─────────────── */
38
+ .member-contact {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: .5rem;
42
+ margin-top: 1rem;
43
+ }
44
+
45
+ .member-contact__icon {
46
+ flex: none;
47
+ width: 1rem;
48
+ height: 1rem;
49
+ color: var(--brand-primary, #2563eb);
50
+ }
51
+
52
+ .member-contact__label {
53
+ font-size: .72rem;
54
+ font-weight: 700;
55
+ letter-spacing: .04em;
56
+ text-transform: uppercase;
57
+ color: var(--brand-muted, #6b7280);
58
+ }
59
+
60
+ .member-detail__back {
61
+ display: inline-block;
62
+ margin-top: 2rem;
63
+ }
64
+
65
+ /* ── Public team grid: photo-cover cards with a gradient + white text ────────── */
66
+ .member-grid {
67
+ display: grid;
68
+ grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
69
+ gap: 1.25rem;
70
+ margin-bottom: 2.75rem;
71
+ }
72
+
73
+ .member-card {
74
+ position: relative;
75
+ display: block;
76
+ aspect-ratio: 3 / 4;
77
+ border-radius: var(--brand-radius, 8px);
78
+ overflow: hidden;
79
+ text-decoration: none;
80
+ background: var(--brand-surface, #f1f1f3);
81
+ box-shadow: 0 1px 3px rgba(0, 0, 0, .1);
82
+ transition: transform .2s ease, box-shadow .2s ease;
83
+ }
84
+
85
+ .member-card:hover {
86
+ transform: translateY(-4px);
87
+ box-shadow: 0 14px 30px rgba(0, 0, 0, .2);
88
+ }
89
+
90
+ .member-card__photo {
91
+ position: absolute;
92
+ inset: 0;
93
+ width: 100%;
94
+ height: 100%;
95
+ object-fit: cover;
96
+ }
97
+
98
+ .member-card__placeholder {
99
+ position: absolute;
100
+ inset: 0;
101
+ display: flex;
102
+ align-items: center;
103
+ justify-content: center;
104
+ font-family: var(--brand-font-display, var(--brand-font-heading, inherit));
105
+ font-size: 3.5rem;
106
+ color: var(--brand-muted, #9aa3af);
107
+ }
108
+
109
+ .member-card__overlay {
110
+ position: absolute;
111
+ inset: 0;
112
+ display: flex;
113
+ flex-direction: column;
114
+ justify-content: flex-end;
115
+ padding: 1.1rem 1.2rem;
116
+ background: linear-gradient(
117
+ 180deg,
118
+ transparent 42%,
119
+ rgba(0, 0, 0, .45) 68%,
120
+ rgba(0, 0, 0, .88) 100%
121
+ );
122
+ }
123
+
124
+ /* Scoped under .member-card so a skin's generic h3 colour can't override white. */
125
+ .member-card .member-card__name {
126
+ margin: 0;
127
+ color: #ffffff;
128
+ font-size: 1.15rem;
129
+ line-height: 1.2;
130
+ text-shadow: 0 1px 8px rgba(0, 0, 0, .55);
131
+ }
132
+
133
+ .member-card .member-card__role {
134
+ margin: .2rem 0 0;
135
+ color: rgba(255, 255, 255, .85);
136
+ font-size: .85rem;
137
+ text-shadow: 0 1px 6px rgba(0, 0, 0, .55);
138
+ }
@@ -0,0 +1,20 @@
1
+ /******/ (() => { // webpackBootstrap
2
+ /*!*************************************************************************************!*\
3
+ !*** ../splent_feature_team/src/splent_io/splent_feature_team/assets/js/scripts.js ***!
4
+ \*************************************************************************************/
5
+ // Entry point for splent_feature_team frontend assets.
6
+ // Add your JavaScript here. Webpack compiles this into assets/dist/splent_feature_team.bundle.js
7
+ //
8
+ // To load the compiled bundle in the product layout, register it in hooks.py:
9
+ //
10
+ // from splent_framework.hooks.template_hooks import register_template_hook
11
+ // from flask import url_for
12
+ //
13
+ // def team_scripts():
14
+ // return '<script src="' + url_for("team.assets", subfolder="dist", filename="splent_feature_team.bundle.js") + '"></script>'
15
+ //
16
+ // register_template_hook("layout.scripts", team_scripts)
17
+
18
+ /******/ })()
19
+ ;
20
+ //# sourceMappingURL=splent_feature_team.bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"splent_feature_team.bundle.js","mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://diversolab_app/../splent_feature_team/src/splent_io/splent_feature_team/assets/js/scripts.js"],"sourcesContent":["// Entry point for splent_feature_team frontend assets.\n// Add your JavaScript here. Webpack compiles this into assets/dist/splent_feature_team.bundle.js\n//\n// To load the compiled bundle in the product layout, register it in hooks.py:\n//\n// from splent_framework.hooks.template_hooks import register_template_hook\n// from flask import url_for\n//\n// def team_scripts():\n// return '<script src=\"' + url_for(\"team.assets\", subfolder=\"dist\", filename=\"splent_feature_team.bundle.js\") + '\"></script>'\n//\n// register_template_hook(\"layout.scripts\", team_scripts)\n"],"names":[],"sourceRoot":""}
@@ -0,0 +1,12 @@
1
+ // Entry point for splent_feature_team frontend assets.
2
+ // Add your JavaScript here. Webpack compiles this into assets/dist/splent_feature_team.bundle.js
3
+ //
4
+ // To load the compiled bundle in the product layout, register it in hooks.py:
5
+ //
6
+ // from splent_framework.hooks.template_hooks import register_template_hook
7
+ // from flask import url_for
8
+ //
9
+ // def team_scripts():
10
+ // return '<script src="' + url_for("team.assets", subfolder="dist", filename="splent_feature_team.bundle.js") + '"></script>'
11
+ //
12
+ // register_template_hook("layout.scripts", team_scripts)
@@ -0,0 +1,14 @@
1
+ const path = require('path');
2
+
3
+ module.exports = {
4
+ entry: path.resolve(__dirname, './scripts.js'),
5
+ output: {
6
+ filename: 'splent_feature_team.bundle.js',
7
+ path: path.resolve(__dirname, '../dist'),
8
+ },
9
+ resolve: {
10
+ fallback: {
11
+ }
12
+ },
13
+ mode: 'development',
14
+ };
@@ -0,0 +1,21 @@
1
+ """
2
+ CLI commands contributed by splent_feature_team.
3
+
4
+ These commands are auto-discovered by the framework and exposed in the
5
+ SPLENT CLI under the ``feature:team`` group.
6
+
7
+ Usage::
8
+
9
+ splent feature:team hello
10
+ """
11
+
12
+ import click
13
+
14
+
15
+ @click.command("hello")
16
+ def hello():
17
+ """Example command — replace with your own."""
18
+ click.echo(" Hello from splent_feature_team!")
19
+
20
+
21
+ cli_commands = [hello]
@@ -0,0 +1,19 @@
1
+ """
2
+ team feature configuration.
3
+
4
+ Injects environment variables into Flask app.config.
5
+ Add your feature's env vars here so the framework can track them.
6
+
7
+ To regenerate from source code: splent feature:inject-config splent_feature_team
8
+ """
9
+
10
+ import os # noqa: F401 — used when adding env vars below
11
+
12
+
13
+ def inject_config(app):
14
+ app.config.update(
15
+ {
16
+ # Add your feature's env vars here, e.g.:
17
+ # "MY_VAR": os.getenv("MY_VAR", "default_value"),
18
+ }
19
+ )
@@ -0,0 +1,6 @@
1
+ from flask_wtf import FlaskForm
2
+ from wtforms import SubmitField
3
+
4
+
5
+ class SplentFeatureTeamForm(FlaskForm):
6
+ submit = SubmitField("Save splent_feature_team")
@@ -0,0 +1,23 @@
1
+ from flask import request, url_for
2
+
3
+ from splent_framework.hooks.template_hooks import register_template_hook
4
+
5
+
6
+ def team_admin_link():
7
+ """Sidebar entry for the Team management screen (the WP-plugin pattern)."""
8
+ active = (
9
+ "active"
10
+ if request.endpoint and request.endpoint.startswith("team.admin")
11
+ else ""
12
+ )
13
+ return (
14
+ f'<li class="sidebar-item {active}">'
15
+ f'<a class="sidebar-link" href="{url_for("team.admin_index")}">'
16
+ '<i class="align-middle" data-feather="users"></i> '
17
+ '<span class="align-middle">Team</span>'
18
+ "</a>"
19
+ "</li>"
20
+ )
21
+
22
+
23
+ register_template_hook("layout.authenticated_sidebar", team_admin_link)
@@ -0,0 +1,36 @@
1
+ [alembic]
2
+ script_location = migrations
3
+
4
+ [loggers]
5
+ keys = root,sqlalchemy,alembic
6
+
7
+ [handlers]
8
+ keys = console
9
+
10
+ [formatters]
11
+ keys = generic
12
+
13
+ [logger_root]
14
+ level = WARN
15
+ handlers = console
16
+ qualname =
17
+
18
+ [logger_sqlalchemy]
19
+ level = WARN
20
+ handlers =
21
+ qualname = sqlalchemy.engine
22
+
23
+ [logger_alembic]
24
+ level = INFO
25
+ handlers =
26
+ qualname = alembic
27
+
28
+ [handler_console]
29
+ class = StreamHandler
30
+ args = (sys.stderr,)
31
+ level = NOTSET
32
+ formatter = generic
33
+
34
+ [formatter_generic]
35
+ format = %(levelname)-5.5s [%(name)s] %(message)s
36
+ datefmt = %H:%M:%S
@@ -0,0 +1,9 @@
1
+ """Alembic migration environment for splent_feature_team."""
2
+
3
+ from splent_io.splent_feature_team import models # noqa
4
+ from splent_framework.migrations.feature_env import run_feature_migrations
5
+
6
+ FEATURE_NAME = "splent_feature_team"
7
+ FEATURE_TABLES = set()
8
+
9
+ run_feature_migrations(FEATURE_NAME, FEATURE_TABLES)
@@ -0,0 +1,24 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ ${imports if imports else ""}
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = ${repr(up_revision)}
14
+ down_revision = ${repr(down_revision)}
15
+ branch_labels = ${repr(branch_labels)}
16
+ depends_on = ${repr(depends_on)}
17
+
18
+
19
+ def upgrade():
20
+ ${upgrades if upgrades else "pass"}
21
+
22
+
23
+ def downgrade():
24
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,46 @@
1
+ """splent_feature_team
2
+
3
+ Revision ID: 1bb39441d98d
4
+ Revises:
5
+ Create Date: 2026-06-27 20:03:41.933507
6
+
7
+ """
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "1bb39441d98d"
15
+ down_revision = None
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ op.create_table(
23
+ "team_member",
24
+ sa.Column("id", sa.Integer(), nullable=False),
25
+ sa.Column("name", sa.String(length=255), nullable=False),
26
+ sa.Column("slug", sa.String(length=255), nullable=False),
27
+ sa.Column("role", sa.String(length=255), nullable=True),
28
+ sa.Column("affiliation", sa.String(length=255), nullable=True),
29
+ sa.Column("group", sa.String(length=128), nullable=True),
30
+ sa.Column("bio", sa.Text(), nullable=True),
31
+ sa.Column("photo", sa.String(length=512), nullable=True),
32
+ sa.Column("email", sa.String(length=255), nullable=True),
33
+ sa.Column("link", sa.String(length=512), nullable=True),
34
+ sa.Column("order", sa.Integer(), nullable=True),
35
+ sa.Column("published", sa.Boolean(), nullable=True),
36
+ sa.PrimaryKeyConstraint("id"),
37
+ )
38
+ op.create_index(op.f("ix_team_member_slug"), "team_member", ["slug"], unique=True)
39
+ # ### end Alembic commands ###
40
+
41
+
42
+ def downgrade():
43
+ # ### commands auto generated by Alembic - please adjust! ###
44
+ op.drop_index(op.f("ix_team_member_slug"), table_name="team_member")
45
+ op.drop_table("team_member")
46
+ # ### end Alembic commands ###
@@ -0,0 +1,29 @@
1
+ """splent_feature_team
2
+
3
+ Revision ID: 3f76830b5164
4
+ Revises: 1bb39441d98d
5
+ Create Date: 2026-06-27 23:28:16.486569
6
+
7
+ """
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "3f76830b5164"
15
+ down_revision = "1bb39441d98d"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ op.add_column("team_member", sa.Column("links", sa.JSON(), nullable=True))
23
+ # ### end Alembic commands ###
24
+
25
+
26
+ def downgrade():
27
+ # ### commands auto generated by Alembic - please adjust! ###
28
+ op.drop_column("team_member", "links")
29
+ # ### end Alembic commands ###
@@ -0,0 +1,101 @@
1
+ """Team roles as many-to-many.
2
+
3
+ Adds a ``role`` table and a ``team_member_role`` pivot, migrates the old
4
+ free-text ``group`` of each member into an associated Role, copies the old
5
+ free-text ``role`` (job title) into a new ``position`` column, and drops the
6
+ ``role``/``group`` columns. Existing members and their sections are preserved.
7
+
8
+ Revision ID: tm0003_roles_m2m
9
+ Revises: 3f76830b5164
10
+ """
11
+
12
+ import re
13
+
14
+ import sqlalchemy as sa
15
+ from alembic import op
16
+
17
+ revision = "tm0003_roles_m2m"
18
+ down_revision = "3f76830b5164"
19
+ branch_labels = None
20
+ depends_on = None
21
+
22
+
23
+ def _slugify(value: str) -> str:
24
+ return re.sub(r"[^a-z0-9]+", "-", (value or "").lower()).strip("-")
25
+
26
+
27
+ def upgrade():
28
+ conn = op.get_bind()
29
+
30
+ # 1. New free-text job title 'position', seeded from the old 'role' column.
31
+ op.add_column(
32
+ "team_member", sa.Column("position", sa.String(length=255), nullable=True)
33
+ )
34
+ op.execute("UPDATE team_member SET position = role")
35
+
36
+ # 2. Role + association tables.
37
+ op.create_table(
38
+ "role",
39
+ sa.Column("id", sa.Integer(), nullable=False),
40
+ sa.Column("name", sa.String(length=128), nullable=False),
41
+ sa.Column("slug", sa.String(length=128), nullable=False),
42
+ sa.Column("order", sa.Integer(), nullable=True),
43
+ sa.PrimaryKeyConstraint("id"),
44
+ )
45
+ op.create_index(op.f("ix_role_slug"), "role", ["slug"], unique=True)
46
+ op.create_table(
47
+ "team_member_role",
48
+ sa.Column("member_id", sa.Integer(), nullable=False),
49
+ sa.Column("role_id", sa.Integer(), nullable=False),
50
+ sa.ForeignKeyConstraint(["member_id"], ["team_member.id"], ondelete="CASCADE"),
51
+ sa.ForeignKeyConstraint(["role_id"], ["role.id"], ondelete="CASCADE"),
52
+ sa.PrimaryKeyConstraint("member_id", "role_id"),
53
+ )
54
+
55
+ # 3. Data migration: one Role per distinct group, then associate members.
56
+ rows = conn.execute(
57
+ sa.text(
58
+ "SELECT DISTINCT `group` FROM team_member "
59
+ "WHERE `group` IS NOT NULL AND `group` <> ''"
60
+ )
61
+ ).fetchall()
62
+ groups = [r[0] for r in rows]
63
+ role_id = {}
64
+ for i, g in enumerate(groups, start=1):
65
+ slug = _slugify(g) or f"role-{i}"
66
+ conn.execute(
67
+ sa.text("INSERT INTO role (name, slug, `order`) VALUES (:n, :s, :o)"),
68
+ {"n": g, "s": slug, "o": i},
69
+ )
70
+ role_id[g] = conn.execute(
71
+ sa.text("SELECT id FROM role WHERE slug = :s"), {"s": slug}
72
+ ).scalar()
73
+
74
+ for mid, g in conn.execute(
75
+ sa.text("SELECT id, `group` FROM team_member")
76
+ ).fetchall():
77
+ if g and g in role_id:
78
+ conn.execute(
79
+ sa.text(
80
+ "INSERT INTO team_member_role (member_id, role_id) VALUES (:m, :r)"
81
+ ),
82
+ {"m": mid, "r": role_id[g]},
83
+ )
84
+
85
+ # 4. Drop the now-migrated columns.
86
+ op.drop_column("team_member", "role")
87
+ op.drop_column("team_member", "group")
88
+
89
+
90
+ def downgrade():
91
+ op.add_column(
92
+ "team_member", sa.Column("group", sa.String(length=128), nullable=True)
93
+ )
94
+ op.add_column(
95
+ "team_member", sa.Column("role", sa.String(length=255), nullable=True)
96
+ )
97
+ op.execute("UPDATE team_member SET role = position")
98
+ op.drop_table("team_member_role")
99
+ op.drop_index(op.f("ix_role_slug"), table_name="role")
100
+ op.drop_table("role")
101
+ op.drop_column("team_member", "position")