Spaces:
Running
Running
Upload 16 files
Browse files- modules/rebalancing.py +35 -53
- modules/utils.py +38 -38
- style.css +17 -4
modules/rebalancing.py
CHANGED
@@ -2,7 +2,7 @@ import math
|
|
2 |
import FinanceDataReader as fdr
|
3 |
import yfinance as yf
|
4 |
from concurrent.futures import ThreadPoolExecutor
|
5 |
-
from modules.utils import load_css, get_currency_symbol, format_quantity,
|
6 |
from collections import defaultdict
|
7 |
|
8 |
def parse_input(holdings, cash_amount):
|
@@ -105,49 +105,16 @@ def generate_portfolio_info(portfolio, total_value, main_currency):
|
|
105 |
|
106 |
# ํ์ฌ ๋น์ค์ ์ฌ์ฉํ์ฌ ํฌํธํด๋ฆฌ์ค ํธ๋ฆฌ๋งต ์ฐจํธ๋ฅผ ์์ฑํฉ๋๋ค.
|
107 |
currunt_weights = {stock_code: details['weight'] for stock_code, details in holdings_totals.items()}
|
108 |
-
|
109 |
|
|
|
|
|
|
|
110 |
# HTML ์์ฑ
|
111 |
portfolio_info = css + f"""
|
112 |
<div class="wrap-text">
|
113 |
-
<div>
|
114 |
-
<div style="margin-bottom: 1.5rem;">
|
115 |
-
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Your Current Portfolio</div>
|
116 |
-
<span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{currency_symbol}{format_value(total_value)}</span> (Before Trades)
|
117 |
-
<hr style="margin: 1.5rem 0;">
|
118 |
-
{current_chart}
|
119 |
-
</div>
|
120 |
-
</div>
|
121 |
-
<br>
|
122 |
<h3>Your Portfolio Holdings</h3>
|
123 |
-
|
124 |
-
<table>
|
125 |
-
<thead>
|
126 |
-
<tr><th>Stock Code</th><th>Current Weight (%)</th><th>Current Value</th></tr>
|
127 |
-
</thead>
|
128 |
-
<tbody>
|
129 |
-
{''.join(
|
130 |
-
f"<tr><td>{stock_code.upper()}</td><td>{details['weight'] * 100:.1f}%</td><td>{currency_symbol}{format_value(details['value'])}</td></tr>"
|
131 |
-
for stock_code, details in holdings_totals.items()
|
132 |
-
)}
|
133 |
-
</tbody>
|
134 |
-
</table>
|
135 |
-
</div>
|
136 |
-
<br>
|
137 |
-
<h3>Your Portfolio by Currency</h3>
|
138 |
-
<div class='table-container wrap-text'>
|
139 |
-
<table>
|
140 |
-
<thead>
|
141 |
-
<tr><th>Currency</th><th>Total Weight (%)</th><th>Total Value</th></tr>
|
142 |
-
</thead>
|
143 |
-
<tbody>
|
144 |
-
{''.join(
|
145 |
-
f"<tr><td>{currency.upper()}</td><td>{details['weight']* 100:.1f}%</td><td>{currency_symbol}{format_value(details['value'])}</td></tr>"
|
146 |
-
for currency, details in currency_totals.items()
|
147 |
-
)}
|
148 |
-
</tbody>
|
149 |
-
</table>
|
150 |
-
</div>
|
151 |
<br>
|
152 |
</div>
|
153 |
"""
|
@@ -216,43 +183,58 @@ def generate_rebalancing_analysis(portfolio, target_ratios, total_value, main_cu
|
|
216 |
'new_value_pct': new_value / new_total_value
|
217 |
})
|
218 |
|
219 |
-
# ์ ๊ท ๋น์ค์ ์ฌ์ฉํ์ฌ ํฌํธํด๋ฆฌ์ค ํธ๋ฆฌ๋งต ์ฐจํธ๋ฅผ ์์ฑํฉ๋๋ค.
|
220 |
-
new_weights = {adj['stock_code']: adj['new_value'] / new_total_value for adj in adjustments}
|
221 |
-
new_chart = plot_treemap(new_weights)
|
222 |
-
|
223 |
# HTML ์์ฑ
|
224 |
rebalancing_analysis = css + f"""
|
225 |
<div class="wrap-text">
|
|
|
226 |
<div style="margin-bottom: 1.5rem;">
|
227 |
-
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Your
|
228 |
<span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{currency_symbol}{format_value(new_total_value)}</span>
|
229 |
(After Trades)
|
230 |
-
<hr style="margin: 1.5rem 0;">
|
231 |
-
{new_chart}
|
232 |
</div>
|
233 |
-
<br>
|
234 |
-
<h3>Trades to Re-Balance Your Portfolio</h3>
|
235 |
<div class='table-container wrap-text'>
|
236 |
<table>
|
237 |
<thead>
|
|
|
|
|
|
|
|
|
|
|
|
|
238 |
<tr>
|
239 |
<th>Stock Code</th>
|
240 |
-
<th>Current
|
|
|
241 |
<th>Target Ratio</th>
|
242 |
<th>Target Weight (%)</th>
|
243 |
<th>Buy or Sell?</th>
|
244 |
<th>Trade Amount - {main_currency} {currency_symbol}</th>
|
245 |
<th>Current Price per Share - {main_currency} {currency_symbol}</th>
|
246 |
<th>Estimated # of Shares to Buy or Sell</th>
|
247 |
-
<th>Quantity of Units</th>
|
248 |
-
<th>Total Value - {main_currency} {currency_symbol}</th>
|
249 |
-
<th>% Asset Allocation</th>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
250 |
</tr>
|
251 |
</thead>
|
252 |
<tbody>
|
253 |
{''.join(
|
254 |
f"<tr>"
|
255 |
-
f"<td>{adj['stock_code'].upper()}</td>"
|
|
|
256 |
f"<td>{adj['current_value_pct'] * 100:.1f}%</td>"
|
257 |
f"<td><span class='highlight-edit'>{adj['target_ratio']}</span></td>"
|
258 |
f"<td>{adj['target_weight'] * 100:.1f}%</td>"
|
|
|
2 |
import FinanceDataReader as fdr
|
3 |
import yfinance as yf
|
4 |
from concurrent.futures import ThreadPoolExecutor
|
5 |
+
from modules.utils import load_css, get_currency_symbol, format_quantity, plot_donut_chart, format_value, current_time
|
6 |
from collections import defaultdict
|
7 |
|
8 |
def parse_input(holdings, cash_amount):
|
|
|
105 |
|
106 |
# ํ์ฌ ๋น์ค์ ์ฌ์ฉํ์ฌ ํฌํธํด๋ฆฌ์ค ํธ๋ฆฌ๋งต ์ฐจํธ๋ฅผ ์์ฑํฉ๋๋ค.
|
107 |
currunt_weights = {stock_code: details['weight'] for stock_code, details in holdings_totals.items()}
|
108 |
+
currunt_weights_chart = plot_donut_chart(currunt_weights)
|
109 |
|
110 |
+
# currency_weights = {currency: details['weight'] for currency, details in currency_totals.items()}
|
111 |
+
# currency_weights_chart = plot_donut_chart(currency_weights)
|
112 |
+
|
113 |
# HTML ์์ฑ
|
114 |
portfolio_info = css + f"""
|
115 |
<div class="wrap-text">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
<h3>Your Portfolio Holdings</h3>
|
117 |
+
{currunt_weights_chart}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
<br>
|
119 |
</div>
|
120 |
"""
|
|
|
183 |
'new_value_pct': new_value / new_total_value
|
184 |
})
|
185 |
|
|
|
|
|
|
|
|
|
186 |
# HTML ์์ฑ
|
187 |
rebalancing_analysis = css + f"""
|
188 |
<div class="wrap-text">
|
189 |
+
<hr style="margin: 1.5rem 0;">
|
190 |
<div style="margin-bottom: 1.5rem;">
|
191 |
+
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Re-Balancing Analysis | Your Portfolio Holdings as of {current_time}</div>
|
192 |
<span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{currency_symbol}{format_value(new_total_value)}</span>
|
193 |
(After Trades)
|
|
|
|
|
194 |
</div>
|
|
|
|
|
195 |
<div class='table-container wrap-text'>
|
196 |
<table>
|
197 |
<thead>
|
198 |
+
<tr>
|
199 |
+
<th colspan="1"></th>
|
200 |
+
<th colspan="2" class="header-bg-before">Your Current Portfolio (Before Trades)</th>
|
201 |
+
<th colspan="6" style='text-align: center'>Trades to Re-Balance Your Portfolio</th>
|
202 |
+
<th colspan="3" class="header-bg-after">Your Adjusted Portfolio (After Trades)</th>
|
203 |
+
</tr>
|
204 |
<tr>
|
205 |
<th>Stock Code</th>
|
206 |
+
<th class="header-bg-before">Current Value - {main_currency} {currency_symbol}</th>
|
207 |
+
<th class="header-bg-before">Current Weight (%)</th>
|
208 |
<th>Target Ratio</th>
|
209 |
<th>Target Weight (%)</th>
|
210 |
<th>Buy or Sell?</th>
|
211 |
<th>Trade Amount - {main_currency} {currency_symbol}</th>
|
212 |
<th>Current Price per Share - {main_currency} {currency_symbol}</th>
|
213 |
<th>Estimated # of Shares to Buy or Sell</th>
|
214 |
+
<th class="header-bg-after">Quantity of Units</th>
|
215 |
+
<th class="header-bg-after">Total Value - {main_currency} {currency_symbol}</th>
|
216 |
+
<th class="header-bg-after">% Asset Allocation</th>
|
217 |
+
</tr>
|
218 |
+
<tr style="font-weight: bold;">
|
219 |
+
<td>Total</td>
|
220 |
+
<td>{format_value(sum(adj['current_value'] for adj in adjustments))}</td>
|
221 |
+
<td>{sum(adj['current_value'] for adj in adjustments) / total_value * 100:.1f}%</td>
|
222 |
+
<td></td>
|
223 |
+
<td></td>
|
224 |
+
<td></td>
|
225 |
+
<td>{format_value(sum(adj['trade_value'] for adj in adjustments))}</td>
|
226 |
+
<td></td>
|
227 |
+
<td></td>
|
228 |
+
<td></td>
|
229 |
+
<td>{format_value(sum(adj['new_value'] for adj in adjustments))}</td>
|
230 |
+
<td>{sum(adj['new_value'] for adj in adjustments) / new_total_value * 100:.1f}%</td>
|
231 |
</tr>
|
232 |
</thead>
|
233 |
<tbody>
|
234 |
{''.join(
|
235 |
f"<tr>"
|
236 |
+
f"<td><span class='highlight-edit'>{adj['stock_code'].upper()}</span></td>"
|
237 |
+
f"<td>{format_value(adj['current_value'])}</td>"
|
238 |
f"<td>{adj['current_value_pct'] * 100:.1f}%</td>"
|
239 |
f"<td><span class='highlight-edit'>{adj['target_ratio']}</span></td>"
|
240 |
f"<td>{adj['target_weight'] * 100:.1f}%</td>"
|
modules/utils.py
CHANGED
@@ -21,17 +21,23 @@ def load_css():
|
|
21 |
|
22 |
|
23 |
def format_quantity(quantity):
|
|
|
|
|
|
|
24 |
if quantity < 0:
|
25 |
return f"({-quantity:,.1f})"
|
26 |
else:
|
27 |
return f"{quantity:,.1f}"
|
28 |
|
29 |
def format_value(value):
|
|
|
|
|
|
|
30 |
if value < 0:
|
31 |
return f"({-value:,.0f})"
|
32 |
else:
|
33 |
return f"{value:,.0f}"
|
34 |
-
|
35 |
currency_symbols = {
|
36 |
"KRW": "โฉ",
|
37 |
"USD": "$",
|
@@ -98,7 +104,7 @@ def create_tab(tab_name, inputs, outputs, update_fn, examples, component_rows):
|
|
98 |
on_change(inputs, update_fn, outputs)
|
99 |
|
100 |
import matplotlib.pyplot as plt
|
101 |
-
import
|
102 |
from io import BytesIO
|
103 |
import base64
|
104 |
from matplotlib import font_manager
|
@@ -106,16 +112,15 @@ from matplotlib import font_manager
|
|
106 |
# Global dictionary to store color mapping
|
107 |
color_map_storage = {}
|
108 |
|
109 |
-
def get_color_for_label(
|
110 |
-
"""Retrieve or generate color for the given
|
111 |
-
if
|
112 |
cmap = plt.get_cmap(color_map)
|
113 |
-
# Generate a color based on
|
114 |
-
index =
|
115 |
-
|
116 |
-
return color_map_storage[label]
|
117 |
|
118 |
-
def
|
119 |
# ๋ฐ์ดํฐ ํํฐ๋ง: ๋น์ค์ด 0์ด ์๋ ํญ๋ชฉ๋ง ์ถ์ถ
|
120 |
filtered_data = {k: v for k, v in data.items() if v > 0}
|
121 |
|
@@ -125,44 +130,39 @@ def plot_treemap(data, color_map='Set3', font_path='Quicksand-Regular.ttf', lege
|
|
125 |
# ๋น์ค์ ๋ฐ๋ผ ๋ฐ์ดํฐ๋ฅผ ์ ๋ ฌ
|
126 |
sorted_data = sorted(filtered_data.items(), key=lambda item: item[1], reverse=True)
|
127 |
labels, sizes = zip(*sorted_data)
|
128 |
-
|
129 |
-
# ๋ผ๋ฒจ์ ๋๋ฌธ์๋ก ๋ณํ
|
130 |
-
labels = [label.upper() for label in labels]
|
131 |
-
sizes = list(sizes)
|
132 |
|
133 |
-
# ํฐํธ ์ค์
|
134 |
-
font_properties = font_manager.FontProperties(fname=font_path, size=legend_fontsize)
|
135 |
-
|
136 |
# ์์ ๋งต์ ์ค์
|
137 |
num_labels = len(labels)
|
|
|
|
|
|
|
138 |
|
139 |
-
|
140 |
-
# ํญ๋ชฉ์ด ํ๋์ผ ๋, ์ ์ฒด ์ฌ๊ฐํ์ ๊ทธ๋ฆฝ๋๋ค.
|
141 |
-
rectangles = [{'x': 0, 'y': 0, 'dx': 100, 'dy': 100}]
|
142 |
-
colors = [get_color_for_label(labels[0], color_map, num_labels)] # ์์ ํ๋๋ก ์ฑ์ฐ๊ธฐ
|
143 |
-
else:
|
144 |
-
norm_sizes = squarify.normalize_sizes(sizes, 100, 100)
|
145 |
-
rectangles = squarify.squarify(norm_sizes, x=0, y=0, dx=100, dy=100)
|
146 |
-
colors = [get_color_for_label(label, color_map, num_labels) for label in labels]
|
147 |
-
|
148 |
-
# ํธ๋ฆฌ๋งต ์๊ฐํ
|
149 |
fig, ax = plt.subplots(figsize=(12, 8), dpi=300) # figsize์ dpi๋ฅผ ์ค์ ํ์ฌ ํด์๋ ๋์ด๊ธฐ
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
# ๋ฒ๋ก ์์ฑ
|
155 |
-
handles = [plt.Line2D([0], [0], marker='
|
156 |
-
markersize=
|
157 |
-
for label, size in zip(labels, sizes)]
|
158 |
|
159 |
# ๋ฒ๋ก ์ถ๊ฐ, ์ ๋ชฉ ์ ๊ฑฐ, ๊ธ์ ํฌ๊ธฐ๋ฅผ ํค์ฐ๊ณ ๋ฒ๋ก ๋ฐ์ค๋ฅผ ์กฐ์
|
160 |
ax.legend(handles=handles, loc='upper left', bbox_to_anchor=(1, 1),
|
161 |
-
prop=
|
162 |
|
163 |
-
|
164 |
-
ax.
|
165 |
-
ax.axis('off') # ์ถ์ ์จ๊น๋๋ค.
|
166 |
|
167 |
# SVG๋ก ์ ์ฅ
|
168 |
buf = BytesIO()
|
|
|
21 |
|
22 |
|
23 |
def format_quantity(quantity):
|
24 |
+
# ์์ฃผ ์์ ๊ฐ์ 0์ผ๋ก ์ฒ๋ฆฌ
|
25 |
+
if abs(quantity) < 1e-5: # ์๊ณ๊ฐ์ ์กฐ์ ํ์ฌ ๋ ์ ์ ํ ๊ฐ์ ์ค์ ํ ์ ์์ต๋๋ค.
|
26 |
+
quantity = 0
|
27 |
if quantity < 0:
|
28 |
return f"({-quantity:,.1f})"
|
29 |
else:
|
30 |
return f"{quantity:,.1f}"
|
31 |
|
32 |
def format_value(value):
|
33 |
+
# ์์ฃผ ์์ ๊ฐ์ 0์ผ๋ก ์ฒ๋ฆฌ
|
34 |
+
if abs(value) < 1e-5: # ์๊ณ๊ฐ์ ์กฐ์ ํ์ฌ ๋ ์ ์ ํ ๊ฐ์ ์ค์ ํ ์ ์์ต๋๋ค.
|
35 |
+
value = 0
|
36 |
if value < 0:
|
37 |
return f"({-value:,.0f})"
|
38 |
else:
|
39 |
return f"{value:,.0f}"
|
40 |
+
|
41 |
currency_symbols = {
|
42 |
"KRW": "โฉ",
|
43 |
"USD": "$",
|
|
|
104 |
on_change(inputs, update_fn, outputs)
|
105 |
|
106 |
import matplotlib.pyplot as plt
|
107 |
+
import numpy as np
|
108 |
from io import BytesIO
|
109 |
import base64
|
110 |
from matplotlib import font_manager
|
|
|
112 |
# Global dictionary to store color mapping
|
113 |
color_map_storage = {}
|
114 |
|
115 |
+
def get_color_for_label(index, color_map, num_labels):
|
116 |
+
"""Retrieve or generate color for the given index."""
|
117 |
+
if index not in color_map_storage:
|
118 |
cmap = plt.get_cmap(color_map)
|
119 |
+
# Generate a color based on index (inverse of the index for color intensity)
|
120 |
+
color_map_storage[index] = cmap(1 - index / (num_labels - 1))
|
121 |
+
return color_map_storage[index]
|
|
|
122 |
|
123 |
+
def plot_donut_chart(data, color_map='Blues', font_path='Quicksand-Regular.ttf', legend_fontsize=30):
|
124 |
# ๋ฐ์ดํฐ ํํฐ๋ง: ๋น์ค์ด 0์ด ์๋ ํญ๋ชฉ๋ง ์ถ์ถ
|
125 |
filtered_data = {k: v for k, v in data.items() if v > 0}
|
126 |
|
|
|
130 |
# ๋น์ค์ ๋ฐ๋ผ ๋ฐ์ดํฐ๋ฅผ ์ ๋ ฌ
|
131 |
sorted_data = sorted(filtered_data.items(), key=lambda item: item[1], reverse=True)
|
132 |
labels, sizes = zip(*sorted_data)
|
|
|
|
|
|
|
|
|
133 |
|
|
|
|
|
|
|
134 |
# ์์ ๋งต์ ์ค์
|
135 |
num_labels = len(labels)
|
136 |
+
|
137 |
+
# ์ํ ์ฐจํธ์ ์์ ๋ฆฌ์คํธ ์์ฑ
|
138 |
+
colors = [get_color_for_label(i, color_map, num_labels) for i in range(num_labels)]
|
139 |
|
140 |
+
# ๋๋ ์ฐจํธ ์๊ฐํ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
fig, ax = plt.subplots(figsize=(12, 8), dpi=300) # figsize์ dpi๋ฅผ ์ค์ ํ์ฌ ํด์๋ ๋์ด๊ธฐ
|
142 |
+
wedges, _ = ax.pie(
|
143 |
+
sizes,
|
144 |
+
colors=colors,
|
145 |
+
labels=[None]*num_labels, # ๋ผ๋ฒจ์ ์์ ๊ธฐ
|
146 |
+
autopct=None, # ๊ฐ ํ์๋ฅผ ์์ ๊ธฐ
|
147 |
+
startangle=-90, # 12์ ๋ฐฉํฅ๋ถํฐ ์์
|
148 |
+
pctdistance=0.85,
|
149 |
+
wedgeprops=dict(width=0.4, edgecolor='w') # ๋๋ ์ฐจํธ
|
150 |
+
)
|
151 |
+
|
152 |
+
# y์ถ ๋ค์ง๊ธฐ
|
153 |
+
ax.invert_yaxis()
|
154 |
+
|
155 |
# ๋ฒ๋ก ์์ฑ
|
156 |
+
handles = [plt.Line2D([0], [0], marker='o', color='w', label=f'{label} {size * 100:.1f}%',
|
157 |
+
markersize=15, markerfacecolor=get_color_for_label(i, color_map, num_labels))
|
158 |
+
for i, (label, size) in enumerate(zip(labels, sizes))]
|
159 |
|
160 |
# ๋ฒ๋ก ์ถ๊ฐ, ์ ๋ชฉ ์ ๊ฑฐ, ๊ธ์ ํฌ๊ธฐ๋ฅผ ํค์ฐ๊ณ ๋ฒ๋ก ๋ฐ์ค๋ฅผ ์กฐ์
|
161 |
ax.legend(handles=handles, loc='upper left', bbox_to_anchor=(1, 1),
|
162 |
+
prop=font_manager.FontProperties(fname=font_path, size=legend_fontsize), frameon=False)
|
163 |
|
164 |
+
# ์ถ์ ์จ๊น๋๋ค.
|
165 |
+
ax.axis('off')
|
|
|
166 |
|
167 |
# SVG๋ก ์ ์ฅ
|
168 |
buf = BytesIO()
|
style.css
CHANGED
@@ -169,13 +169,23 @@
|
|
169 |
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); */
|
170 |
}
|
171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
.table-container table {
|
173 |
width: 100%;
|
174 |
border-collapse: collapse;
|
175 |
}
|
176 |
|
177 |
.table-container th, .table-container td {
|
178 |
-
border: 1px
|
179 |
padding: 8px;
|
180 |
text-align: left;
|
181 |
background-color: var(--background-color-light);
|
@@ -214,12 +224,15 @@
|
|
214 |
/* ์ด๋์ด ํ
๋ง ์ ์ฉ */
|
215 |
@media (prefers-color-scheme: dark) {
|
216 |
.table-container {
|
217 |
-
border: 1px
|
|
|
|
|
|
|
|
|
218 |
}
|
219 |
-
|
220 |
.table-container th,
|
221 |
.table-container td {
|
222 |
-
border: 1px
|
223 |
background: #333;
|
224 |
color: #ccc;
|
225 |
}
|
|
|
169 |
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); */
|
170 |
}
|
171 |
|
172 |
+
.header-bg-before {
|
173 |
+
background-color: #e7f9ef !important; /* ์ํ๋ ์์์ผ๋ก ๋ณ๊ฒฝ */
|
174 |
+
color: #000; /* ํ
์คํธ ์์ */
|
175 |
+
}
|
176 |
+
|
177 |
+
.header-bg-after {
|
178 |
+
background-color: #d9d2e9 !important;/* ์ํ๋ ์์์ผ๋ก ๋ณ๊ฒฝ */
|
179 |
+
color: #000; /* ํ
์คํธ ์์ */
|
180 |
+
}
|
181 |
+
|
182 |
.table-container table {
|
183 |
width: 100%;
|
184 |
border-collapse: collapse;
|
185 |
}
|
186 |
|
187 |
.table-container th, .table-container td {
|
188 |
+
border: 1px solid #ddd;
|
189 |
padding: 8px;
|
190 |
text-align: left;
|
191 |
background-color: var(--background-color-light);
|
|
|
224 |
/* ์ด๋์ด ํ
๋ง ์ ์ฉ */
|
225 |
@media (prefers-color-scheme: dark) {
|
226 |
.table-container {
|
227 |
+
border: 1px solid #444;
|
228 |
+
}
|
229 |
+
.header-bg-before,
|
230 |
+
.header-bg-after {
|
231 |
+
color: #000 !important;
|
232 |
}
|
|
|
233 |
.table-container th,
|
234 |
.table-container td {
|
235 |
+
border: 1px solid #444;
|
236 |
background: #333;
|
237 |
color: #ccc;
|
238 |
}
|